sequelizer 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +10 -12
- data/lib/sequel/extensions/cold_col.rb +11 -19
- data/lib/sequel/extensions/db_opts.rb +2 -8
- data/lib/sequel/extensions/funky.rb +75 -22
- data/lib/sequel/extensions/make_readyable.rb +10 -18
- data/lib/sequel/extensions/more_sql.rb +2 -6
- data/lib/sequel/extensions/platform.rb +19 -35
- data/lib/sequel/extensions/settable.rb +25 -12
- data/lib/sequel/extensions/smart_select_remove.rb +79 -21
- data/lib/sequel/extensions/sql_recorder.rb +0 -4
- data/lib/sequel/extensions/unionize.rb +2 -10
- data/lib/sequel/extensions/usable.rb +0 -4
- data/lib/sequelizer/cli.rb +15 -36
- data/lib/sequelizer/connection_maker.rb +42 -11
- data/lib/sequelizer/env_config.rb +2 -4
- data/lib/sequelizer/options.rb +75 -67
- data/lib/sequelizer/version.rb +1 -3
- data/lib/sequelizer/yaml_config.rb +10 -13
- data/lib/sequelizer.rb +14 -12
- metadata +36 -51
- data/lib/sequelizer/gemfile_modifier.rb +0 -91
- data/lib/sequelizer/monkey_patches/database_in_after_connect.rb +0 -28
- data/lib/sequelizer/options_hash.rb +0 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9cda95a3cd5386a1d2e0ce1402793044ba78a07f457a265efe7a45e930095cb0
|
|
4
|
+
data.tar.gz: 5cbfe2a32b9b65f9922d47fa3a9e894e2335874dfed53fd972bc9d9483ee9dd5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3657c7dfd5f9a040cf0b38caa910c1e5526ff874367fc261eaa2da3c3cebfaba9ffc9ca511e183466b10d019d822cfecd9e4b12e3fcbe07e59fc8075608a98de
|
|
7
|
+
data.tar.gz: 29dd4df1852d028fea492621110a08a65cf186fe672a18645c6f4832aa89c5803c514db68bcd125dc8341e892d24273e0d11eee9f16c0f154cc10c59a967a02a
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.4.0] - 2026-10-25
|
|
6
|
+
|
|
7
|
+
### Breaking changes
|
|
8
|
+
|
|
9
|
+
- **options:** Passed schema wins, and a caller's after_connect still runs ([8656edc](https://github.com/outcomesinsights/sequelizer/commit/8656edc9bf9d7a5d8f3c8242139c9f779af9b3e0)): when a lower-precedence source (.env, ENV or YAML) sets search_path and the caller passes schema: or schema_search_path:, the passed value is now the search path. Before, the lower-precedence search_path was used. A source that set schema while a lower-precedence source set search_path changes the same way.
|
|
10
|
+
- **funky:** Hash takes a named strategy, and md5 is the default ([5a8ef4e](https://github.com/outcomesinsights/sequelizer/commit/5a8ef4e6df604f40a943f15a30c1618fe08db27a)): funky.hash returns different ids by default. Pass strategy: :legacy to get the ids that earlier versions built.
|
|
11
|
+
- **smart_select_remove:** Raise AllColumnsRemovedError when every column is removed ([d8eeca4](https://github.com/outcomesinsights/sequelizer/commit/d8eeca42bbb5e365290bc4ea911e150ba9799d1f)): code that removed every column with select_remove got SELECT * and now raises AllColumnsRemovedError.
|
|
12
|
+
- **smart_select_remove:** Raise NoColumnsError instead of producing SELECT * ([810a0c7](https://github.com/outcomesinsights/sequelizer/commit/810a0c7fe8406683a185b129797d9818ba4c871e)): code that got SELECT * from select_remove on a database that returns no columns now raises NoColumnsError. conceptql loads this extension and builds SQL on Sequel.mock(host: :postgres) in its tests, so it may see this error when it moves to sequelizer 0.4.
|
|
13
|
+
- Drop hashie and OptionsHash for symbol-keyed hashes ([490c924](https://github.com/outcomesinsights/sequelizer/commit/490c924cba0c87d331607a49ed7dcb5963741e36)): Sequelizer.options and Options#to_hash now return a plain Hash with Symbol keys; look keys up with symbols (h[:adapter]).
|
|
14
|
+
- Remove the update_gemfile command and GemfileModifier ([4837ff4](https://github.com/outcomesinsights/sequelizer/commit/4837ff470477b8e092afaa9de612bf3cf7582580)): the update_gemfile command and Sequelizer::GemfileModifier are removed; use `bundle add <adapter gem>` instead.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **settable:** Quote Sequel expression values and make set_sql public ([5882d41](https://github.com/outcomesinsights/sequelizer/commit/5882d416429d88a5cc08a391e249e51d76399770))
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **deps:** Drop the activesupport runtime dependency ([3380d25](https://github.com/outcomesinsights/sequelizer/commit/3380d25805b5608fc87226eaf8a2521262aaa655))
|
|
23
|
+
- **deps:** Drop the pp dependency and require ([8f186b3](https://github.com/outcomesinsights/sequelizer/commit/8f186b34c2011382ed12a67e166dab909e99d93f))
|
|
24
|
+
- Delete empty ConnectionMaker#configure_adapter_specific_options ([33da14e](https://github.com/outcomesinsights/sequelizer/commit/33da14ed0ad8acd41026d4c9e27cba6c62fb1a2b))
|
|
25
|
+
- Apply db_opts without patching ConnectionPool#make_new ([3507790](https://github.com/outcomesinsights/sequelizer/commit/350779069a72e7def64717dbb93eb2266e1cde00))
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **sequelizer:** A URL string at new_db and db means { url: string } ([a2e6091](https://github.com/outcomesinsights/sequelizer/commit/a2e6091f735d9f496f521430fb9942898e4bae50))
|
|
30
|
+
- **connection_maker:** Raise MissingAdapterError when nothing is configured ([c531242](https://github.com/outcomesinsights/sequelizer/commit/c531242e84c66390effa145cea5e4412eabaaaf4))
|
|
31
|
+
- **options:** Accept a database URL string as the url option ([73c5f61](https://github.com/outcomesinsights/sequelizer/commit/73c5f61df0c3c2037605d3b53d9c301e087b1c32))
|
|
32
|
+
- **smart_select_remove:** Resolve an explicit select list without querying the database ([f23c851](https://github.com/outcomesinsights/sequelizer/commit/f23c851f1f3ef987bc41869d43e04bb776db44fc))
|
|
33
|
+
|
|
5
34
|
## [0.3.0] - 2026-10-01
|
|
6
35
|
|
|
7
36
|
### Breaking changes
|
|
@@ -142,3 +171,5 @@ All notable changes to this project will be documented in this file.
|
|
|
142
171
|
### Added
|
|
143
172
|
|
|
144
173
|
- The project itself
|
|
174
|
+
|
|
175
|
+
[0.4.0]: https://github.com/outcomesinsights/sequelizer/compare/v0.3.0...v0.4.0
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@ I was tired of writing the code to bootstrap a connection to my databases.
|
|
|
4
4
|
|
|
5
5
|
[Sequel](https://github.com/jeremyevans/sequel/) provides an easy mechanism for connecting to a database, but I didn't want to store my username/password/other sensitive information in the scripts I was writing.
|
|
6
6
|
|
|
7
|
-
So I wrote this gem that lets me store my database configuration options in either config/
|
|
7
|
+
So I wrote this gem that lets me store my database configuration options in either config/sequelizer.yml or .env and then lets me call `db` to get a connection to my database.
|
|
8
8
|
|
|
9
9
|
I normally use this gem when I'm writing a quick script or a Thor-based command line utility.
|
|
10
10
|
|
|
@@ -30,7 +30,7 @@ $ gem install sequelizer
|
|
|
30
30
|
|
|
31
31
|
## Usage
|
|
32
32
|
|
|
33
|
-
To get the most out of Sequelizer, you'll need to create a config/
|
|
33
|
+
To get the most out of Sequelizer, you'll need to create a config/sequelizer.yml file or a .env file and specify your database configuration options in that file.
|
|
34
34
|
|
|
35
35
|
Sequelizer comes with a handy command that will print out your connection parameters. Just run:
|
|
36
36
|
|
|
@@ -38,16 +38,12 @@ Sequelizer comes with a handy command that will print out your connection parame
|
|
|
38
38
|
bundle exec sequelizer config
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
You'll also need to make sure the gem for your database is installed. You can do this by adding the gem to your application's Gemfile.
|
|
42
|
-
|
|
43
|
-
Sequelizer comes with a handy command that will update your Gemfile for you. Once you've specified your database configuration, run
|
|
41
|
+
You'll also need to make sure the gem for your database is installed. You can do this by adding the gem to your application's Gemfile, e.g. for PostgreSQL:
|
|
44
42
|
|
|
45
43
|
```
|
|
46
|
-
bundle
|
|
44
|
+
bundle add pg
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
The command will look up the right gem to use with the adapter you've specified, add a line to your Gemfile specifying that gem, and run `bundle install` to install the gem for you.
|
|
50
|
-
|
|
51
47
|
Once you've specified your options and made sure your database's gem is installed, simply include the Sequelizer module in any class that needs a database connection and you'll get two handy-dandy methods: `db` and `new_db`
|
|
52
48
|
|
|
53
49
|
Observe:
|
|
@@ -70,15 +66,17 @@ end
|
|
|
70
66
|
|
|
71
67
|
`new_db` will create a new connection to the database on each call.
|
|
72
68
|
|
|
73
|
-
Both take a hash of database options if you don't want to create a config/
|
|
69
|
+
Both take a hash of database options, or a database URL string such as `"postgres://localhost/my_db"`, if you don't want to create a config/sequelizer.yml or .env file, or simply wish to override those options. Options are merged together from all sources with the following precedence:
|
|
74
70
|
|
|
75
71
|
```
|
|
76
|
-
passed_options > .env > manually defined environment variables > config/
|
|
72
|
+
passed_options > .env > manually defined environment variables > config/sequelizer.yml > ~/.config/sequelizer/database.yml
|
|
77
73
|
```
|
|
78
74
|
|
|
79
|
-
So if config/
|
|
75
|
+
So if config/sequelizer.yml specifies a connection, you can set an environment variable (either manually, or through .env) to override one of those options. Similarly, if you pass an option to the method directly, that option will override the YAML and ENV-based options. See #3 for further discussion.
|
|
76
|
+
|
|
77
|
+
For PostgreSQL, `search_path`, `schema_search_path` and `schema` are three names for one option. The highest-precedence source that sets any of them decides the search path, so a `schema:` you pass overrides a `search_path` from .env or YAML. If one source sets more than one name, `search_path` wins over `schema_search_path`, which wins over `schema`. Sequelizer creates the schemas and sets the search path on each new connection, and then runs any `after_connect` you pass.
|
|
80
78
|
|
|
81
|
-
Take a look at the examples directory for a few ways you can specify your database configuration options.
|
|
79
|
+
Take a look at the examples directory for a few ways you can specify your database configuration options. Copy a `sequelizer*.yml` example to `config/sequelizer.yml` to use it.
|
|
82
80
|
|
|
83
81
|
## Frustrations
|
|
84
82
|
|
|
@@ -38,18 +38,12 @@
|
|
|
38
38
|
#
|
|
39
39
|
# DB.extension :cold_col
|
|
40
40
|
|
|
41
|
-
require 'active_support/core_ext/object/try'
|
|
42
|
-
require 'active_support/core_ext/object/blank'
|
|
43
|
-
|
|
44
41
|
module Sequel
|
|
45
|
-
|
|
46
42
|
module ColdColDatabase
|
|
47
|
-
|
|
48
43
|
# Internal schema registry for managing column information across different sources.
|
|
49
44
|
# This class centralizes the storage and retrieval of table/view column metadata
|
|
50
45
|
# from multiple sources including created tables, views, and manually loaded schemas.
|
|
51
46
|
class SchemaRegistry
|
|
52
|
-
|
|
53
47
|
# Initialize a new schema registry for the given database.
|
|
54
48
|
#
|
|
55
49
|
# @param db [Sequel::Database] the database instance this registry belongs to
|
|
@@ -99,7 +93,7 @@ module Sequel
|
|
|
99
93
|
literal_name = @db.literal(name)
|
|
100
94
|
table_name = name_to_string(name)
|
|
101
95
|
|
|
102
|
-
[@created_views, @created_tables, @schemas].each do |registry|
|
|
96
|
+
[ @created_views, @created_tables, @schemas ].each do |registry|
|
|
103
97
|
next unless registry
|
|
104
98
|
|
|
105
99
|
if (columns = Sequel.synchronize { registry[literal_name] })
|
|
@@ -145,7 +139,6 @@ module Sequel
|
|
|
145
139
|
name.to_s
|
|
146
140
|
end
|
|
147
141
|
end
|
|
148
|
-
|
|
149
142
|
end
|
|
150
143
|
|
|
151
144
|
# Sets up the cold column tracking when the extension is loaded
|
|
@@ -165,8 +158,8 @@ module Sequel
|
|
|
165
158
|
def load_schema(path)
|
|
166
159
|
schema_data = Psych.load_file(path) || {}
|
|
167
160
|
schemas = schema_data.to_h do |table, info|
|
|
168
|
-
columns = (info[:columns] || {}).map { |column_name, col_info| [column_name.to_sym, col_info] }
|
|
169
|
-
[table.to_s, columns]
|
|
161
|
+
columns = (info[:columns] || {}).map { |column_name, col_info| [ column_name.to_sym, col_info ] }
|
|
162
|
+
[ table.to_s, columns ]
|
|
170
163
|
end
|
|
171
164
|
cold_col_registry.merge_schemas(schemas)
|
|
172
165
|
end
|
|
@@ -223,13 +216,11 @@ module Sequel
|
|
|
223
216
|
end
|
|
224
217
|
|
|
225
218
|
def columns_from_generator(generator)
|
|
226
|
-
generator.columns.map { |c| [c[:name], c] }
|
|
219
|
+
generator.columns.map { |c| [ c[:name], c ] }
|
|
227
220
|
end
|
|
228
|
-
|
|
229
221
|
end
|
|
230
222
|
|
|
231
223
|
module ColdColDataset
|
|
232
|
-
|
|
233
224
|
# Return the columns for the dataset without executing a query
|
|
234
225
|
def columns
|
|
235
226
|
columns_search
|
|
@@ -249,7 +240,7 @@ module Sequel
|
|
|
249
240
|
|
|
250
241
|
protected
|
|
251
242
|
|
|
252
|
-
WILDCARD = Sequel.lit(
|
|
243
|
+
WILDCARD = Sequel.lit("*").freeze
|
|
253
244
|
|
|
254
245
|
# Determine the probable columns for a dataset based on its query options.
|
|
255
246
|
# This is the main entry point for column determination logic.
|
|
@@ -259,7 +250,7 @@ module Sequel
|
|
|
259
250
|
def probable_columns(opts_chain)
|
|
260
251
|
cols = opts_chain[:select]
|
|
261
252
|
|
|
262
|
-
return columns_from_sources(opts_chain) if cols.
|
|
253
|
+
return columns_from_sources(opts_chain) if cols.nil? || cols.empty?
|
|
263
254
|
|
|
264
255
|
columns_from_select_list(cols, opts_chain)
|
|
265
256
|
end
|
|
@@ -398,7 +389,7 @@ module Sequel
|
|
|
398
389
|
def find_cte_columns(name, opts_chain)
|
|
399
390
|
current_opts = opts_chain
|
|
400
391
|
|
|
401
|
-
while current_opts.
|
|
392
|
+
while current_opts && !current_opts.empty?
|
|
402
393
|
with = (current_opts[:with] || []).detect { |wh| literal(wh[:name]) == literal(name) }
|
|
403
394
|
return with[:dataset].columns_search(opts_chain) if with
|
|
404
395
|
|
|
@@ -415,7 +406,10 @@ module Sequel
|
|
|
415
406
|
# @param opts_chain [Hash] the dataset's query options
|
|
416
407
|
# @return [Array<Symbol>, nil] column names if found, nil otherwise
|
|
417
408
|
def find_join_alias(name, opts_chain)
|
|
418
|
-
join = (opts_chain[:join] || []).detect
|
|
409
|
+
join = (opts_chain[:join] || []).detect do |jc|
|
|
410
|
+
table_alias = jc.table_expr.alias if jc.table_expr.respond_to?(:alias)
|
|
411
|
+
literal(table_alias) == literal(name)
|
|
412
|
+
end
|
|
419
413
|
return nil unless join
|
|
420
414
|
|
|
421
415
|
join_expr = join.table_expr.expression
|
|
@@ -452,9 +446,7 @@ module Sequel
|
|
|
452
446
|
a.is_a?(SQL::Identifier) ? a.value.to_sym : a.to_sym
|
|
453
447
|
end
|
|
454
448
|
end
|
|
455
|
-
|
|
456
449
|
end
|
|
457
450
|
|
|
458
451
|
Database.register_extension(:cold_col, Sequel::ColdColDatabase)
|
|
459
|
-
|
|
460
452
|
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
module Sequel
|
|
2
|
-
|
|
3
2
|
# = DbOpts
|
|
4
3
|
#
|
|
5
4
|
# Sequel extension that provides database-specific options handling.
|
|
@@ -19,10 +18,8 @@ module Sequel
|
|
|
19
18
|
# SET work_mem='256MB'
|
|
20
19
|
# SET shared_preload_libraries='pg_stat_statements'
|
|
21
20
|
module DbOpts
|
|
22
|
-
|
|
23
21
|
# Handles extraction and application of database-specific options.
|
|
24
22
|
class DbOptions
|
|
25
|
-
|
|
26
23
|
# @!attribute [r] db
|
|
27
24
|
# @return [Sequel::Database] the database instance
|
|
28
25
|
attr_reader :db
|
|
@@ -53,7 +50,7 @@ module Sequel
|
|
|
53
50
|
|
|
54
51
|
db.opts.select do |k, _|
|
|
55
52
|
k.to_s.match(opt_regexp)
|
|
56
|
-
end.to_h { |k, v| [k.to_s.gsub(opt_regexp,
|
|
53
|
+
end.to_h { |k, v| [ k.to_s.gsub(opt_regexp, "").to_sym, prep_value(k, v) ] }
|
|
57
54
|
end
|
|
58
55
|
|
|
59
56
|
# Applies the database options to the given connection.
|
|
@@ -83,9 +80,8 @@ module Sequel
|
|
|
83
80
|
#
|
|
84
81
|
# @return [Array<String>] array of SQL SET statements
|
|
85
82
|
def sql_statements
|
|
86
|
-
db.
|
|
83
|
+
db.set_sql(to_hash)
|
|
87
84
|
end
|
|
88
|
-
|
|
89
85
|
end
|
|
90
86
|
|
|
91
87
|
# Returns a DbOptions instance for this database.
|
|
@@ -94,9 +90,7 @@ module Sequel
|
|
|
94
90
|
def db_opts
|
|
95
91
|
@db_opts ||= DbOptions.new(self)
|
|
96
92
|
end
|
|
97
|
-
|
|
98
93
|
end
|
|
99
94
|
|
|
100
95
|
Database.register_extension(:db_opts, DbOpts)
|
|
101
|
-
|
|
102
96
|
end
|
|
@@ -1,43 +1,81 @@
|
|
|
1
1
|
module Sequel
|
|
2
|
-
|
|
3
2
|
module Funky
|
|
3
|
+
# Raised when funky.hash is given a strategy name it does not know.
|
|
4
|
+
class UnknownHashStrategyError < Sequel::Error
|
|
5
|
+
def initialize(strategy)
|
|
6
|
+
super("unknown funky.hash strategy #{strategy.inspect}; known strategies: #{FunkyBase::HASH_STRATEGIES.map(&:inspect).join(", ")}")
|
|
7
|
+
end
|
|
8
|
+
end
|
|
4
9
|
|
|
5
10
|
class FunkyBase
|
|
11
|
+
# The names funky.hash accepts. A strategy's definition never changes,
|
|
12
|
+
# because a change would change every id built with it; a new
|
|
13
|
+
# definition gets a new name.
|
|
14
|
+
HASH_STRATEGIES = %i[md5 legacy].freeze
|
|
15
|
+
|
|
16
|
+
# ASCII unit separator: joins the parts in the md5 strategy.
|
|
17
|
+
HASH_SEPARATOR = 31
|
|
18
|
+
|
|
19
|
+
# ASCII record separator: stands in for a NULL part in the md5 strategy.
|
|
20
|
+
HASH_NULL_MARKER = 30
|
|
6
21
|
|
|
7
22
|
def initialize(db)
|
|
8
23
|
@db = db
|
|
9
24
|
end
|
|
10
25
|
|
|
26
|
+
# Returns a signed 64-bit integer expression that hashes +parts+.
|
|
27
|
+
#
|
|
28
|
+
# strategy: :md5 (the default) casts each part to a string, writes a NULL
|
|
29
|
+
# part as chr(30), joins the parts with chr(31), and reads the first 16
|
|
30
|
+
# hex characters of their md5 as a signed integer. It gives the same id
|
|
31
|
+
# on DuckDB and on Spark.
|
|
32
|
+
#
|
|
33
|
+
# strategy: :legacy is each database's form before :md5 existed:
|
|
34
|
+
# hash(concat(...)) on DuckDB and xxhash64(...) on Spark. Use it only to
|
|
35
|
+
# reproduce ids built with it; on DuckDB it gives duplicate ids.
|
|
36
|
+
def hash(*parts, strategy: :md5)
|
|
37
|
+
case strategy
|
|
38
|
+
when :md5 then md5_hash(parts)
|
|
39
|
+
when :legacy then legacy_hash(parts)
|
|
40
|
+
else raise UnknownHashStrategyError, strategy
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
11
44
|
def to_strptime(format)
|
|
12
45
|
return format if format =~ /%/
|
|
13
46
|
|
|
14
|
-
format.gsub(
|
|
47
|
+
format.gsub("yyyy", "%Y").gsub("MM", "%m").gsub("dd", "%d")
|
|
15
48
|
end
|
|
16
49
|
|
|
17
50
|
def from_strptime(format)
|
|
18
51
|
return format unless format =~ /%/
|
|
19
52
|
|
|
20
|
-
format.gsub(
|
|
53
|
+
format.gsub("%Y", "yyyy").gsub("%m", "MM").gsub("%d", "dd")
|
|
21
54
|
end
|
|
22
55
|
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# The first 16 hex characters of the md5 of the joined parts.
|
|
59
|
+
def md5_hex16(parts)
|
|
60
|
+
strings = parts.map do |part|
|
|
61
|
+
Sequel.function(:coalesce, Sequel.cast_string(part), Sequel.function(:chr, HASH_NULL_MARKER))
|
|
62
|
+
end
|
|
63
|
+
joined = Sequel.function(:concat_ws, Sequel.function(:chr, HASH_SEPARATOR), *strings)
|
|
64
|
+
Sequel.function(:substr, Sequel.function(:md5, joined), 1, 16)
|
|
65
|
+
end
|
|
23
66
|
end
|
|
24
67
|
|
|
25
68
|
class FunkySpark < FunkyBase
|
|
26
|
-
|
|
27
69
|
def str_to_date(value, format, try: false) # rubocop:disable Lint/UnusedMethodArgument
|
|
28
70
|
Sequel.function(:to_date, Sequel.cast_string(value), format)
|
|
29
71
|
end
|
|
30
72
|
|
|
31
|
-
def hash(*)
|
|
32
|
-
Sequel.function(:xxhash64, *)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
73
|
def make_json_column(ds, key_column, value_column)
|
|
36
74
|
json_object_col = Sequel.function(
|
|
37
75
|
:named_struct,
|
|
38
|
-
|
|
76
|
+
"key",
|
|
39
77
|
key_column,
|
|
40
|
-
|
|
78
|
+
"value",
|
|
41
79
|
value_column,
|
|
42
80
|
).then do |json_object_col|
|
|
43
81
|
Sequel.function(
|
|
@@ -64,24 +102,24 @@ module Sequel
|
|
|
64
102
|
Sequel.function(:collect_list, column)
|
|
65
103
|
end
|
|
66
104
|
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
# conv with a negative target base reads the hex as a signed number.
|
|
108
|
+
def md5_hash(parts)
|
|
109
|
+
Sequel.function(:conv, md5_hex16(parts), 16, -10).cast(:bigint)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def legacy_hash(parts)
|
|
113
|
+
Sequel.function(:xxhash64, *parts)
|
|
114
|
+
end
|
|
67
115
|
end
|
|
68
116
|
|
|
69
117
|
class FunkyDuckDB < FunkyBase
|
|
70
|
-
|
|
71
118
|
def str_to_date(value, format, try: false)
|
|
72
119
|
strptime_func = try ? :try_strptime : :strptime
|
|
73
120
|
Sequel.function(strptime_func, Sequel.cast_string(value), to_strptime(format))
|
|
74
121
|
end
|
|
75
122
|
|
|
76
|
-
def hash(*)
|
|
77
|
-
Sequel.function(:hash, concat(*)).then do |hash_val|
|
|
78
|
-
Sequel.case(
|
|
79
|
-
{ hash_val <= 9_223_372_036_854_775_807 => hash_val },
|
|
80
|
-
hash_val - 18_446_744_073_709_551_616,
|
|
81
|
-
).cast(:bigint)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
123
|
def concat(*)
|
|
86
124
|
Sequel.function(:concat, *)
|
|
87
125
|
end
|
|
@@ -106,6 +144,23 @@ module Sequel
|
|
|
106
144
|
Sequel.function(:list, column)
|
|
107
145
|
end
|
|
108
146
|
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def md5_hash(parts)
|
|
150
|
+
signed(Sequel.cast(Sequel.join([ "0x", md5_hex16(parts) ]), :ubigint))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def legacy_hash(parts)
|
|
154
|
+
signed(Sequel.function(:hash, concat(*parts)))
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Folds a UBIGINT into the BIGINT with the same 64 bits.
|
|
158
|
+
def signed(ubigint)
|
|
159
|
+
Sequel.case(
|
|
160
|
+
{ ubigint <= 9_223_372_036_854_775_807 => ubigint },
|
|
161
|
+
ubigint - 18_446_744_073_709_551_616,
|
|
162
|
+
).cast(:bigint)
|
|
163
|
+
end
|
|
109
164
|
end
|
|
110
165
|
|
|
111
166
|
def self.extended(db)
|
|
@@ -128,9 +183,7 @@ module Sequel
|
|
|
128
183
|
raise "No known functions for #{database_type}"
|
|
129
184
|
end
|
|
130
185
|
end
|
|
131
|
-
|
|
132
186
|
end
|
|
133
187
|
|
|
134
188
|
Database.register_extension(:funky, Funky)
|
|
135
|
-
|
|
136
189
|
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
module Sequel
|
|
2
|
-
|
|
3
2
|
# = MakeReadyable
|
|
4
3
|
#
|
|
5
4
|
# Sequel extension that provides database readiness functionality,
|
|
@@ -17,7 +16,6 @@ module Sequel
|
|
|
17
16
|
# @example External file sources
|
|
18
17
|
# db.make_ready(search_path: [Pathname.new('data.parquet')])
|
|
19
18
|
module MakeReadyable
|
|
20
|
-
|
|
21
19
|
# Prepares the database by setting up schemas, views, and external data sources.
|
|
22
20
|
#
|
|
23
21
|
# This method is primarily geared towards Spark SQL-based databases.
|
|
@@ -48,7 +46,6 @@ module Sequel
|
|
|
48
46
|
def make_ready(opts = {})
|
|
49
47
|
ReadyMaker.new(self, opts).run
|
|
50
48
|
end
|
|
51
|
-
|
|
52
49
|
end
|
|
53
50
|
|
|
54
51
|
# = ReadyMaker
|
|
@@ -57,7 +54,6 @@ module Sequel
|
|
|
57
54
|
# This class processes the make_ready options and executes the necessary
|
|
58
55
|
# SQL statements to set up schemas, views, and external data sources.
|
|
59
56
|
class ReadyMaker
|
|
60
|
-
|
|
61
57
|
# @!attribute [r] db
|
|
62
58
|
# @return [Sequel::Database] the database instance
|
|
63
59
|
# @!attribute [r] opts
|
|
@@ -146,7 +142,6 @@ module Sequel
|
|
|
146
142
|
# This class creates temporary views that read from external files
|
|
147
143
|
# like Parquet, ORC, etc.
|
|
148
144
|
class FileSourcerer
|
|
149
|
-
|
|
150
145
|
# @!attribute [r] db
|
|
151
146
|
# @return [Sequel::Database] the database instance
|
|
152
147
|
# @!attribute [r] schema
|
|
@@ -167,7 +162,7 @@ module Sequel
|
|
|
167
162
|
# @param _opts [Hash] unused options parameter
|
|
168
163
|
# @return [Array<Symbol>] array containing the table name
|
|
169
164
|
def tables(_opts = {})
|
|
170
|
-
[schema.basename(schema.extname).to_s.to_sym]
|
|
165
|
+
[ schema.basename(schema.extname).to_s.to_sym ]
|
|
171
166
|
end
|
|
172
167
|
|
|
173
168
|
# Creates a temporary view that reads from the external file.
|
|
@@ -181,7 +176,7 @@ module Sequel
|
|
|
181
176
|
db.create_view(table, {
|
|
182
177
|
temp: true,
|
|
183
178
|
using: format,
|
|
184
|
-
options: { path: schema.expand_path }
|
|
179
|
+
options: { path: schema.expand_path }
|
|
185
180
|
}.merge(opts))
|
|
186
181
|
when :duckdb
|
|
187
182
|
# DuckDB uses direct file reading with read_* functions
|
|
@@ -199,20 +194,20 @@ module Sequel
|
|
|
199
194
|
# @param _opts [Hash] additional options to merge (currently unused for DuckDB)
|
|
200
195
|
def create_duckdb_view(table, _opts)
|
|
201
196
|
file_path = if schema.directory?
|
|
202
|
-
schema.expand_path.join(
|
|
203
|
-
|
|
197
|
+
schema.expand_path.join("**").join("*.#{format}").to_s
|
|
198
|
+
else
|
|
204
199
|
schema.expand_path.to_s
|
|
205
|
-
|
|
200
|
+
end
|
|
206
201
|
read_function = case format
|
|
207
|
-
|
|
202
|
+
when "parquet"
|
|
208
203
|
:read_parquet
|
|
209
|
-
|
|
204
|
+
when "csv"
|
|
210
205
|
:read_csv_auto
|
|
211
|
-
|
|
206
|
+
when "json"
|
|
212
207
|
:read_json_auto
|
|
213
|
-
|
|
208
|
+
else
|
|
214
209
|
raise Sequel::Error, "Unsupported file format '#{format}' for DuckDB"
|
|
215
|
-
|
|
210
|
+
end
|
|
216
211
|
|
|
217
212
|
# DuckDB doesn't support TEMPORARY views, use regular CREATE VIEW
|
|
218
213
|
db.create_view(table, db.from(Sequel.function(read_function, file_path)))
|
|
@@ -224,11 +219,8 @@ module Sequel
|
|
|
224
219
|
def format
|
|
225
220
|
schema.extname[1..]
|
|
226
221
|
end
|
|
227
|
-
|
|
228
222
|
end
|
|
229
|
-
|
|
230
223
|
end
|
|
231
224
|
|
|
232
225
|
Database.register_extension(:make_readyable, MakeReadyable)
|
|
233
|
-
|
|
234
226
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Sequel
|
|
4
|
-
|
|
5
4
|
# Provides additional SQL helper methods for database operations.
|
|
6
5
|
#
|
|
7
6
|
# The more_sql extension adds convenience methods for SQL operations that
|
|
@@ -19,7 +18,6 @@ module Sequel
|
|
|
19
18
|
# DB.create_schema(:staging, if_not_exists: true)
|
|
20
19
|
# # Executes: CREATE SCHEMA IF NOT EXISTS "staging"
|
|
21
20
|
module MoreSql
|
|
22
|
-
|
|
23
21
|
# Creates a database schema.
|
|
24
22
|
#
|
|
25
23
|
# Generates and executes a CREATE SCHEMA statement with optional
|
|
@@ -63,14 +61,12 @@ module Sequel
|
|
|
63
61
|
# create_schema_sql(:test, if_not_exists: true)
|
|
64
62
|
# # => 'CREATE SCHEMA IF NOT EXISTS "test"'
|
|
65
63
|
def create_schema_sql(schema_name, opts)
|
|
66
|
-
sql =
|
|
67
|
-
sql +=
|
|
64
|
+
sql = "CREATE SCHEMA "
|
|
65
|
+
sql += "IF NOT EXISTS " if opts[:if_not_exists]
|
|
68
66
|
sql += literal(schema_name)
|
|
69
67
|
sql
|
|
70
68
|
end
|
|
71
|
-
|
|
72
69
|
end
|
|
73
70
|
|
|
74
71
|
Database.register_extension(:more_sql, MoreSql)
|
|
75
|
-
|
|
76
72
|
end
|