lib/sequel/adapters/shared/xtdb.rb in sequel-xtdb-0.2.1 vs lib/sequel/adapters/shared/xtdb.rb in sequel-xtdb-0.3.0

- old
+ new

@@ -2,11 +2,103 @@ module XTDB module DatabaseMethods def database_type :xtdb end + + def primary_key(_table) + # eg used for RETURNING on insert (prevents crash) + :_id + end + + # Get a dataset with `current`, `valid` and `system` set. + # + # For selects this creates the SETTING pre-amble, e.g. 'SETTING DEFAULT VALID_TIME ...': + # ``` + # DB.as_of(current: 2.weeks.ago).select(Sequel.lit('current_timestamp')).single_value + # ``` + # + # A block can be provided as a convenience to stay in SQL-land (selects only): + # ``` + # DB.as_of(current: 2.hours.ago) do + # DB["select current_timestamp"] + # end.sql + # => + # SETTING + # CURRENT_TIME TO TIMESTAMP '2024-12-17T12:59:48+01:00' + # select current_timestamp + # ``` + # + # When doing inserts, the `_valid_from` will be added (if not provided): + # ``` + # DB[:products].as_of(valid: 2.weeks.ago).insert(_id: 1, name: 'Spam') + # ``` + def as_of(...) + ds = @default_dataset.as_of(...) + return ds unless block_given? + + yield.clone(append_sql: ds.select_setting_sql("")) + end end module DatasetMethods + Dataset.def_sql_method(self, :select, + [["if opts[:values]", + %w[values compounds order limit]], + ["else", + %w[setting select distinct columns from join where group having compounds order limit lock]]]) + + def as_of(valid: nil, system: nil, current: nil) + {valid: valid, system: system, current: current}.reject { |_k, v| v.nil? }.then do |opts| + clone(opts) + end + end + + def server_version + 0 + end + + def insert_values_sql(sql) + if (from_ix = opts[:columns].index(:_valid_from)) + opts[:values][from_ix] = Sequel.lit("TIMESTAMP ?", opts[:values][from_ix]) + end + + if (to_ix = opts[:columns].index(:_valid_to)) + opts[:values][to_ix] = Sequel.lit("TIMESTAMP ?", opts[:values][to_ix]) + end + + super + end + + def insert_columns_sql(sql) + if opts[:valid] && !opts[:columns].index(:_valid_from) + opts[:columns] << :_valid_from + opts[:values] << opts[:valid] + end + super + end + + def select_setting_sql(sql) + setting = opts.slice(:current, :valid, :system) + return sql if setting.empty? + + cast_value = ->(v) do + case v + when DateTime, Time + literal_append "TIMESTAMP ", v.iso8601 + when Date + literal_append "DATE ", v.iso8601 + end + end + sql << "SETTING " + sql << setting.map do |k, v| + if k == :current + literal_append "CURRENT_TIME TO TIMESTAMP ", v.iso8601 + else + "DEFAULT #{k.upcase}_TIME AS OF #{cast_value[v]}" + end + end.join(", ") + sql << " " + end end end end