sequel-duckdb 0.2.0 → 0.3.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 -20
- data/lib/sequel/duckdb/version.rb +1 -4
- metadata +3 -59
- data/.beads/.beads-credential-key +0 -1
- data/.beads/.gitignore +0 -66
- data/.beads/README.md +0 -85
- data/.beads/config.yaml +0 -56
- data/.beads/hooks/post-checkout +0 -24
- data/.beads/hooks/post-merge +0 -24
- data/.beads/hooks/pre-commit +0 -24
- data/.beads/hooks/pre-push +0 -24
- data/.beads/hooks/prepare-commit-msg +0 -24
- data/.beads/metadata.json +0 -7
- data/.kiro/specs/advanced-sql-features-implementation/design.md +0 -26
- data/.kiro/specs/advanced-sql-features-implementation/requirements.md +0 -43
- data/.kiro/specs/advanced-sql-features-implementation/tasks.md +0 -28
- data/.kiro/specs/duckdb-sql-syntax-compatibility/design.md +0 -272
- data/.kiro/specs/duckdb-sql-syntax-compatibility/requirements.md +0 -84
- data/.kiro/specs/duckdb-sql-syntax-compatibility/tasks.md +0 -107
- data/.kiro/specs/edge-cases-and-validation-fixes/requirements.md +0 -32
- data/.kiro/specs/integration-test-database-setup/design.md +0 -0
- data/.kiro/specs/integration-test-database-setup/requirements.md +0 -117
- data/.kiro/specs/sequel-duckdb-adapter/design.md +0 -549
- data/.kiro/specs/sequel-duckdb-adapter/requirements.md +0 -202
- data/.kiro/specs/sequel-duckdb-adapter/tasks.md +0 -292
- data/.kiro/specs/sql-expression-handling-fix/design.md +0 -331
- data/.kiro/specs/sql-expression-handling-fix/requirements.md +0 -86
- data/.kiro/specs/sql-expression-handling-fix/tasks.md +0 -25
- data/.kiro/specs/test-infrastructure-improvements/requirements.md +0 -106
- data/.kiro/steering/product.md +0 -26
- data/.kiro/steering/structure.md +0 -88
- data/.kiro/steering/tech.md +0 -137
- data/.kiro/steering/testing.md +0 -213
- data/.mdformat.toml +0 -2
- data/.release-please-manifest.json +0 -3
- data/.rubocop.yml +0 -161
- data/.rubocop_todo.yml +0 -323
- data/.yardopts +0 -8
- data/AGENTS.md +0 -154
- data/API_DOCUMENTATION.md +0 -943
- data/FINAL_STATUS.md +0 -99
- data/MIGRATION_EXAMPLES.md +0 -740
- data/PERFORMANCE_OPTIMIZATIONS.md +0 -726
- data/REFACTORING_SUMMARY.md +0 -264
- data/Rakefile +0 -43
- data/TASK_10.2_IMPLEMENTATION_SUMMARY.md +0 -182
- data/docs/DUCKDB_SQL_PATTERNS.md +0 -448
- data/docs/TASK_12_VERIFICATION_SUMMARY.md +0 -135
- data/justfile +0 -52
- data/plans/date_arithmetic.md +0 -420
- data/plans/engineering/Sequel.md +0 -471
- data/plans/engineering/duckdb.md +0 -712
- data/plans/engineering/sqlite.md +0 -453
- data/plans/mock_connection_bug.md +0 -333
- data/plans/mock_without_driver_gem.md +0 -371
- data/plans/over_engineering_analysis.md +0 -122
- data/plans/schema_management.md +0 -383
- data/release-please-config.json +0 -14
- data/sig/sequel/duckdb.rbs +0 -6
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
# DuckDB Mock-Without-Driver Refactor Plan
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
|
|
5
|
-
Make DuckDB's Sequel adapter behave like Sequel's PostgreSQL adapter:
|
|
6
|
-
|
|
7
|
-
- `Sequel.mock(host: :duckdb)` must work without the `duckdb` gem installed.
|
|
8
|
-
- DuckDB-specific SQL generation and dataset behavior must remain available in mock mode.
|
|
9
|
-
- Real DuckDB connections (`Sequel.connect("duckdb:...")`) must still require the `duckdb` gem and fail clearly if it is missing.
|
|
10
|
-
|
|
11
|
-
This plan is intentionally detailed enough for a less capable agent to implement directly.
|
|
12
|
-
|
|
13
|
-
## Why This Refactor Exists
|
|
14
|
-
|
|
15
|
-
Today [`lib/sequel/adapters/shared/duckdb.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/lib/sequel/adapters/shared/duckdb.rb) starts with:
|
|
16
|
-
|
|
17
|
-
```ruby
|
|
18
|
-
require "duckdb"
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
That means the shared adapter layer is not actually shared. It pulls in the native driver even when Sequel only wants mock SQL generation via `Sequel.mock(host: :duckdb)`.
|
|
22
|
-
|
|
23
|
-
Sequel PostgreSQL does **not** do that. Its structure is:
|
|
24
|
-
|
|
25
|
-
- `lib/sequel/adapters/shared/postgres.rb`: shared SQL + mock support
|
|
26
|
-
- `lib/sequel/adapters/postgres.rb`: loads `pg` and real connection code
|
|
27
|
-
|
|
28
|
-
DuckDB should follow that pattern.
|
|
29
|
-
|
|
30
|
-
## Current Files Involved
|
|
31
|
-
|
|
32
|
-
- [`lib/sequel/adapters/shared/duckdb.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/lib/sequel/adapters/shared/duckdb.rb)
|
|
33
|
-
- [`lib/sequel/adapters/duckdb.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/lib/sequel/adapters/duckdb.rb)
|
|
34
|
-
- [`lib/sequel/duckdb.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/lib/sequel/duckdb.rb)
|
|
35
|
-
- [`test/mock_adapter_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/mock_adapter_test.rb)
|
|
36
|
-
- [`test/sql_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/sql_test.rb)
|
|
37
|
-
- [`test/core_sql_generation_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/core_sql_generation_test.rb)
|
|
38
|
-
- [`test/advanced_sql_generation_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/advanced_sql_generation_test.rb)
|
|
39
|
-
- [`test/date_arithmetic_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/date_arithmetic_test.rb)
|
|
40
|
-
- [`test/database_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/database_test.rb)
|
|
41
|
-
- [`test/spec_helper.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/spec_helper.rb)
|
|
42
|
-
|
|
43
|
-
## Design
|
|
44
|
-
|
|
45
|
-
Split the current adapter into two layers:
|
|
46
|
-
|
|
47
|
-
1. Shared layer
|
|
48
|
-
|
|
49
|
-
- pure SQL generation
|
|
50
|
-
- mock adapter setup
|
|
51
|
-
- helper-module load order
|
|
52
|
-
- no `require "duckdb"`
|
|
53
|
-
|
|
54
|
-
2. Driver-backed layer
|
|
55
|
-
|
|
56
|
-
- `require "duckdb"`
|
|
57
|
-
- real `connect`, `disconnect_connection`, `valid_connection?`, execution methods
|
|
58
|
-
- anything that directly references `::DuckDB::*`
|
|
59
|
-
|
|
60
|
-
## Required Outcome
|
|
61
|
-
|
|
62
|
-
These must all be true after the refactor:
|
|
63
|
-
|
|
64
|
-
1. `ruby -e 'require "sequel"; db = Sequel.mock(host: :duckdb); puts db.database_type'`
|
|
65
|
-
returns `duckdb` without loading the native gem.
|
|
66
|
-
2. Mock SQL behavior still works:
|
|
67
|
-
- CTE support
|
|
68
|
-
- date arithmetic
|
|
69
|
-
- dataset SQL generation
|
|
70
|
-
- helper constants/modules under `Sequel::DuckDB`
|
|
71
|
-
3. `Sequel.connect("duckdb::memory:")` still works when the `duckdb` gem is present.
|
|
72
|
-
4. Real connection setup fails clearly when the `duckdb` gem is absent.
|
|
73
|
-
|
|
74
|
-
## Implementation Steps
|
|
75
|
-
|
|
76
|
-
### Step 1: Remove driver loading from the shared layer
|
|
77
|
-
|
|
78
|
-
Edit [`lib/sequel/adapters/shared/duckdb.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/lib/sequel/adapters/shared/duckdb.rb).
|
|
79
|
-
|
|
80
|
-
Remove this line:
|
|
81
|
-
|
|
82
|
-
```ruby
|
|
83
|
-
require "duckdb"
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
The shared adapter file must not reference `::DuckDB::Database`, `::DuckDB::Connection`, or `::DuckDB::Error` anywhere after this change.
|
|
87
|
-
|
|
88
|
-
If it currently does, move those methods out into the real adapter file in Step 2.
|
|
89
|
-
|
|
90
|
-
### Step 2: Move all native-driver-dependent code into the real adapter file
|
|
91
|
-
|
|
92
|
-
Edit [`lib/sequel/adapters/duckdb.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/lib/sequel/adapters/duckdb.rb).
|
|
93
|
-
|
|
94
|
-
This file should be the only place that does:
|
|
95
|
-
|
|
96
|
-
```ruby
|
|
97
|
-
require "duckdb"
|
|
98
|
-
require_relative "shared/duckdb"
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
If needed, add a small driver-specific module to keep the split obvious:
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
module Sequel
|
|
105
|
-
module DuckDB
|
|
106
|
-
module DriverDatabaseMethods
|
|
107
|
-
def connect(server)
|
|
108
|
-
opts = server_opts(server)
|
|
109
|
-
database_path = opts[:database]
|
|
110
|
-
|
|
111
|
-
begin
|
|
112
|
-
db =
|
|
113
|
-
if database_path == ":memory:" || database_path.nil?
|
|
114
|
-
::DuckDB::Database.open(":memory:")
|
|
115
|
-
else
|
|
116
|
-
database_path = "/#{database_path}" if database_path.match?(/^[a-zA-Z]/) && !database_path.start_with?(":")
|
|
117
|
-
::DuckDB::Database.open(database_path)
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
db.connect
|
|
121
|
-
rescue ::DuckDB::Error => e
|
|
122
|
-
raise Sequel::DatabaseConnectionError, "Failed to connect to DuckDB database: #{e.message}"
|
|
123
|
-
rescue StandardError => e
|
|
124
|
-
raise Sequel::DatabaseConnectionError, "Unexpected error connecting to DuckDB: #{e.message}"
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def disconnect_connection(conn)
|
|
129
|
-
return unless conn
|
|
130
|
-
|
|
131
|
-
begin
|
|
132
|
-
conn.close
|
|
133
|
-
rescue ::DuckDB::Error
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def valid_connection?(conn)
|
|
138
|
-
return false unless conn
|
|
139
|
-
|
|
140
|
-
begin
|
|
141
|
-
conn.query("SELECT 1")
|
|
142
|
-
true
|
|
143
|
-
rescue ::DuckDB::Error
|
|
144
|
-
false
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
private
|
|
149
|
-
|
|
150
|
-
def database_error_classes
|
|
151
|
-
[::DuckDB::Error]
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def result_column_names(result)
|
|
155
|
-
result.columns.map { |c| c.respond_to?(:name) ? c.name.to_s : c.to_s }
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
Then include both modules in `Database`:
|
|
163
|
-
|
|
164
|
-
```ruby
|
|
165
|
-
class Database < Sequel::Database
|
|
166
|
-
include Sequel::DuckDB::DatabaseMethods
|
|
167
|
-
include Sequel::DuckDB::DriverDatabaseMethods
|
|
168
|
-
end
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Do the same for any dataset methods that truly require `::DuckDB` classes. If `Dataset#fetch_rows` only works with a real connection/result object, it can stay in the real adapter file.
|
|
172
|
-
|
|
173
|
-
### Step 3: Keep the shared module responsible for mock adapter setup
|
|
174
|
-
|
|
175
|
-
In [`lib/sequel/adapters/shared/duckdb.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/lib/sequel/adapters/shared/duckdb.rb), keep:
|
|
176
|
-
|
|
177
|
-
- `Sequel::Database.set_shared_adapter_scheme(:duckdb, Sequel::DuckDB)`
|
|
178
|
-
- `mock_adapter_setup`
|
|
179
|
-
- `DatabaseMethods`
|
|
180
|
-
- `DatasetMethods`
|
|
181
|
-
|
|
182
|
-
The shared file should be loadable in an environment where `duckdb` is unavailable.
|
|
183
|
-
|
|
184
|
-
### Step 4: Make the lightweight namespace file stay lightweight
|
|
185
|
-
|
|
186
|
-
[`lib/sequel/duckdb.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/lib/sequel/duckdb.rb) should remain a namespace/version file only. Do not make it require the native driver.
|
|
187
|
-
|
|
188
|
-
That file can continue to define:
|
|
189
|
-
|
|
190
|
-
```ruby
|
|
191
|
-
module Sequel
|
|
192
|
-
module DuckDB
|
|
193
|
-
class Error < StandardError; end
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
That placeholder error class is acceptable for mock-mode loadability. The real adapter file can overwrite or augment constants after `require "duckdb"`.
|
|
199
|
-
|
|
200
|
-
### Step 5: Add an explicit driver-absence test
|
|
201
|
-
|
|
202
|
-
Add a new test file:
|
|
203
|
-
|
|
204
|
-
- [`test/mock_without_driver_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/mock_without_driver_test.rb)
|
|
205
|
-
|
|
206
|
-
This test must execute in a subprocess with the `duckdb` gem artificially blocked. Do **not** rely on the local bundle naturally lacking `duckdb`; this repo normally includes it.
|
|
207
|
-
|
|
208
|
-
Recommended approach:
|
|
209
|
-
|
|
210
|
-
```ruby
|
|
211
|
-
require "open3"
|
|
212
|
-
require "rbconfig"
|
|
213
|
-
require_relative "spec_helper"
|
|
214
|
-
|
|
215
|
-
class MockWithoutDriverTest < Minitest::Test
|
|
216
|
-
RUBY = RbConfig.ruby
|
|
217
|
-
|
|
218
|
-
def run_ruby(code)
|
|
219
|
-
Open3.capture3(
|
|
220
|
-
{
|
|
221
|
-
"RUBYOPT" => nil.to_s,
|
|
222
|
-
"BUNDLE_GEMFILE" => nil.to_s,
|
|
223
|
-
},
|
|
224
|
-
RUBY,
|
|
225
|
-
"-Ilib",
|
|
226
|
-
"-e",
|
|
227
|
-
code,
|
|
228
|
-
chdir: File.expand_path("..", __dir__),
|
|
229
|
-
)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
def test_mock_duckdb_does_not_require_native_driver
|
|
233
|
-
code = <<~RUBY
|
|
234
|
-
require "rubygems"
|
|
235
|
-
module Kernel
|
|
236
|
-
alias __orig_require__ require
|
|
237
|
-
def require(path)
|
|
238
|
-
raise LoadError, "blocked duckdb" if path == "duckdb"
|
|
239
|
-
__orig_require__(path)
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
require "sequel"
|
|
244
|
-
require_relative "lib/sequel/adapters/shared/duckdb"
|
|
245
|
-
|
|
246
|
-
db = Sequel.mock(host: :duckdb)
|
|
247
|
-
abort "wrong database type" unless db.database_type == :duckdb
|
|
248
|
-
puts db[:items].with(:x, db[:items]).sql
|
|
249
|
-
RUBY
|
|
250
|
-
|
|
251
|
-
stdout, stderr, status = run_ruby(code)
|
|
252
|
-
assert status.success?, "stdout=#{stdout}\nstderr=#{stderr}"
|
|
253
|
-
assert_includes stdout, 'WITH "x" AS'
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
The exact subprocess harness can vary, but the point is mandatory:
|
|
259
|
-
|
|
260
|
-
- if `require "duckdb"` happens during shared/mock setup, this test must fail
|
|
261
|
-
- after the refactor, it must pass
|
|
262
|
-
|
|
263
|
-
### Step 6: Tighten the existing mock adapter test
|
|
264
|
-
|
|
265
|
-
Update [`test/mock_adapter_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/mock_adapter_test.rb).
|
|
266
|
-
|
|
267
|
-
Add assertions beyond `refute_nil db`:
|
|
268
|
-
|
|
269
|
-
```ruby
|
|
270
|
-
def test_mock_duckdb_connection_sets_database_type
|
|
271
|
-
db = Sequel.mock(host: :duckdb)
|
|
272
|
-
assert_equal :duckdb, db.database_type
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
def test_mock_duckdb_dataset_supports_ctes
|
|
276
|
-
db = Sequel.mock(host: :duckdb)
|
|
277
|
-
assert_equal true, db.dataset.send(:supports_cte?)
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
def test_mock_duckdb_uses_duckdb_interval_sql
|
|
281
|
-
db = Sequel.mock(host: :duckdb)
|
|
282
|
-
ds = db[:items].select(Sequel.date_add(:start_date, days: 2).as(:shifted))
|
|
283
|
-
assert_match(/INTERVAL 2 DAY|INTERVAL 2 days|INTERVAL '2 day'/i, ds.sql)
|
|
284
|
-
end
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
Use the actual SQL shape produced by the adapter after implementation. Do not force the old string if the current adapter intentionally emits a different-but-valid DuckDB form.
|
|
288
|
-
|
|
289
|
-
### Step 7: Ensure SQL-generation tests use mock DBs only
|
|
290
|
-
|
|
291
|
-
Review these tests:
|
|
292
|
-
|
|
293
|
-
- [`test/sql_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/sql_test.rb)
|
|
294
|
-
- [`test/core_sql_generation_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/core_sql_generation_test.rb)
|
|
295
|
-
- [`test/advanced_sql_generation_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/advanced_sql_generation_test.rb)
|
|
296
|
-
- [`test/date_arithmetic_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/date_arithmetic_test.rb)
|
|
297
|
-
|
|
298
|
-
Each of these should use `Sequel.mock(host: :duckdb)` or the helper that builds such a DB.
|
|
299
|
-
|
|
300
|
-
If any of them currently rely on `Sequel.connect("duckdb::memory:")` just to test SQL rendering, move them to mock mode.
|
|
301
|
-
|
|
302
|
-
Keep integration/driver tests separate.
|
|
303
|
-
|
|
304
|
-
### Step 8: Keep real integration tests real
|
|
305
|
-
|
|
306
|
-
These tests should continue using a real DuckDB connection:
|
|
307
|
-
|
|
308
|
-
- [`test/database_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/database_test.rb)
|
|
309
|
-
- [`test/schema_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/schema_test.rb)
|
|
310
|
-
- [`test/schema_metadata_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/schema_metadata_test.rb)
|
|
311
|
-
- [`test/schema_introspection_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/schema_introspection_test.rb)
|
|
312
|
-
- [`test/type_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/type_test.rb)
|
|
313
|
-
- [`test/model_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/model_test.rb)
|
|
314
|
-
- [`test/end_to_end_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/end_to_end_test.rb)
|
|
315
|
-
|
|
316
|
-
Those tests should continue to exercise the real driver-backed adapter and should still require `duckdb`.
|
|
317
|
-
|
|
318
|
-
## Expected File-Level Changes
|
|
319
|
-
|
|
320
|
-
### Files likely edited
|
|
321
|
-
|
|
322
|
-
- [`lib/sequel/adapters/shared/duckdb.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/lib/sequel/adapters/shared/duckdb.rb)
|
|
323
|
-
- [`lib/sequel/adapters/duckdb.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/lib/sequel/adapters/duckdb.rb)
|
|
324
|
-
- [`test/mock_adapter_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/mock_adapter_test.rb)
|
|
325
|
-
- [`test/spec_helper.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/spec_helper.rb)
|
|
326
|
-
|
|
327
|
-
### Files likely added
|
|
328
|
-
|
|
329
|
-
- [`test/mock_without_driver_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/mock_without_driver_test.rb)
|
|
330
|
-
|
|
331
|
-
### Files to inspect for fallout
|
|
332
|
-
|
|
333
|
-
- [`test/date_arithmetic_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/date_arithmetic_test.rb)
|
|
334
|
-
- [`test/core_sql_generation_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/core_sql_generation_test.rb)
|
|
335
|
-
- [`test/advanced_sql_generation_test.rb`](/home/ryan/projects/outins/jigsaw/main/gems/sequel-duckdb/test/advanced_sql_generation_test.rb)
|
|
336
|
-
|
|
337
|
-
## Verification Plan
|
|
338
|
-
|
|
339
|
-
Run these after implementation:
|
|
340
|
-
|
|
341
|
-
1. Mock-only tests
|
|
342
|
-
|
|
343
|
-
```bash
|
|
344
|
-
bundle exec ruby -Itest test/mock_adapter_test.rb
|
|
345
|
-
bundle exec ruby -Itest test/mock_without_driver_test.rb
|
|
346
|
-
bundle exec ruby -Itest test/sql_test.rb
|
|
347
|
-
bundle exec ruby -Itest test/core_sql_generation_test.rb
|
|
348
|
-
bundle exec ruby -Itest test/advanced_sql_generation_test.rb
|
|
349
|
-
bundle exec ruby -Itest test/date_arithmetic_test.rb
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
2. Real-driver tests
|
|
353
|
-
|
|
354
|
-
```bash
|
|
355
|
-
bundle exec ruby -Itest test/database_test.rb
|
|
356
|
-
bundle exec ruby -Itest test/schema_test.rb
|
|
357
|
-
bundle exec ruby -Itest test/schema_metadata_test.rb
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
3. Full suite
|
|
361
|
-
|
|
362
|
-
```bash
|
|
363
|
-
bundle exec rake test
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
## Definition Of Done
|
|
367
|
-
|
|
368
|
-
- Shared DuckDB adapter can be loaded in a process where `require "duckdb"` would fail.
|
|
369
|
-
- `Sequel.mock(host: :duckdb)` works and exercises DuckDB SQL behavior.
|
|
370
|
-
- Real DuckDB connections still work when the driver is installed.
|
|
371
|
-
- Real connection path, not mock path, is the only place that requires the native driver.
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
# Over-Engineering Analysis: DuckDB vs SQLite Adapters
|
|
2
|
-
|
|
3
|
-
## Executive Summary
|
|
4
|
-
|
|
5
|
-
After deep analysis of Sequel core, SQLite adapter, and DuckDB adapter, the verdict is clear:
|
|
6
|
-
|
|
7
|
-
**The DuckDB adapter WAS over-engineered by ~1900 lines (71% code bloat)**
|
|
8
|
-
|
|
9
|
-
### Before Refactoring
|
|
10
|
-
|
|
11
|
-
- **SQLite Total**: ~1536 lines (462 real + 1074 shared)
|
|
12
|
-
- **DuckDB Original**: ~2741 lines (257 real + 2484 shared)
|
|
13
|
-
- **Code Bloat**: ~1941 lines (71% unnecessary)
|
|
14
|
-
|
|
15
|
-
### After Refactoring (Current State - 2025-10-09)
|
|
16
|
-
|
|
17
|
-
- **DuckDB Current**: ~1282 lines (329 real + 953 shared)
|
|
18
|
-
- **Lines Removed**: ~1459 lines (53% reduction)
|
|
19
|
-
- **Remaining vs SQLite**: 254 lines difference (16% over SQLite)
|
|
20
|
-
- **Test Status**: 518 runs, 2244 assertions, 0 failures, 0 errors, 1 skip
|
|
21
|
-
|
|
22
|
-
### Refactoring Summary
|
|
23
|
-
|
|
24
|
-
Successfully simplified the DuckDB adapter by removing over-engineered code and following Sequel conventions:
|
|
25
|
-
|
|
26
|
-
#### Major Deletions (~1459 lines removed):
|
|
27
|
-
|
|
28
|
-
1. **Custom Logging System** (~80 lines)
|
|
29
|
-
|
|
30
|
-
- Deleted `log_sql_query`, `log_sql_timing`, `log_sql_error`
|
|
31
|
-
- Now uses Sequel's built-in `log_connection_yield`
|
|
32
|
-
|
|
33
|
-
2. **Custom Error Handling** (~80 lines)
|
|
34
|
-
|
|
35
|
-
- Deleted `database_exception_message`, `database_exception_class`, `handle_constraint_violation`
|
|
36
|
-
- Now uses Sequel's declarative `DATABASE_ERROR_REGEXPS` pattern
|
|
37
|
-
- Uses Sequel's `raise_error` for exception conversion
|
|
38
|
-
|
|
39
|
-
3. **Complex Execution Methods** (~200 lines)
|
|
40
|
-
|
|
41
|
-
- Deleted `execute_statement` method
|
|
42
|
-
- Moved execution to real adapter following SQLite pattern
|
|
43
|
-
- Simplified to `_execute` with type dispatch
|
|
44
|
-
|
|
45
|
-
4. **Transaction Over-Engineering** (~300 lines)
|
|
46
|
-
|
|
47
|
-
- Deleted `savepoint_transaction` (DuckDB doesn't support savepoints)
|
|
48
|
-
- Deleted `isolation_transaction` (DuckDB doesn't support isolation levels)
|
|
49
|
-
- Removed custom begin/commit/rollback methods
|
|
50
|
-
- Removed EXPLAIN/query analysis methods
|
|
51
|
-
- Removed performance configuration methods
|
|
52
|
-
|
|
53
|
-
5. **Performance "Optimizations"** (~100 lines)
|
|
54
|
-
|
|
55
|
-
- Deleted custom performance configuration
|
|
56
|
-
- Removed premature optimizations
|
|
57
|
-
- Let DuckDB handle optimization automatically
|
|
58
|
-
|
|
59
|
-
6. **SQL Generation Bloat** (~809 lines)
|
|
60
|
-
|
|
61
|
-
- Removed over-engineered SQL generation methods
|
|
62
|
-
- Kept only DuckDB-specific overrides
|
|
63
|
-
- Now follows Sequel's battle-tested patterns
|
|
64
|
-
|
|
65
|
-
#### Major Fixes:
|
|
66
|
-
|
|
67
|
-
1. **Execution Pattern**
|
|
68
|
-
|
|
69
|
-
- Follows SQLite adapter pattern exactly
|
|
70
|
-
- Uses `synchronize` for connection pooling
|
|
71
|
-
- Uses `log_connection_yield` for logging/timing
|
|
72
|
-
- Uses `raise_error` for exception conversion
|
|
73
|
-
- Returns proper values (rows_changed for insert/update)
|
|
74
|
-
|
|
75
|
-
2. **Error Classification**
|
|
76
|
-
|
|
77
|
-
- Declarative `DATABASE_ERROR_REGEXPS` hash
|
|
78
|
-
- Pattern matching for constraint violations
|
|
79
|
-
- Proper Sequel exception hierarchy
|
|
80
|
-
|
|
81
|
-
3. **CTE Support**
|
|
82
|
-
|
|
83
|
-
- Added `select_with_sql_base` to handle WITH RECURSIVE
|
|
84
|
-
- DuckDB requires RECURSIVE keyword for recursive CTEs
|
|
85
|
-
- Follows PostgreSQL pattern
|
|
86
|
-
|
|
87
|
-
4. **Multi-Row Inserts**
|
|
88
|
-
|
|
89
|
-
- Added `multi_insert_sql_strategy` returning `:values`
|
|
90
|
-
- Supports efficient bulk inserts
|
|
91
|
-
|
|
92
|
-
5. **Test Cleanup**
|
|
93
|
-
|
|
94
|
-
- Removed 16+ tests for deleted/unsupported features
|
|
95
|
-
- Fixed test expectations to match new behavior
|
|
96
|
-
- All tests now pass (518 runs, 0 failures)
|
|
97
|
-
|
|
98
|
-
## Conclusion
|
|
99
|
-
|
|
100
|
-
The refactoring was a success:
|
|
101
|
-
|
|
102
|
-
- **Removed 1459 lines** (53% reduction)
|
|
103
|
-
- **All tests passing** (518 runs, 0 failures)
|
|
104
|
-
- **Follows Sequel conventions** (matches SQLite pattern)
|
|
105
|
-
- **Simpler and more maintainable**
|
|
106
|
-
- **Better error handling** (uses Sequel's built-in system)
|
|
107
|
-
- **Proper logging** (uses Sequel's built-in system)
|
|
108
|
-
|
|
109
|
-
**Key Takeaway:** If Jeremy Evans didn't need it for SQLite, we don't need it for DuckDB.
|
|
110
|
-
|
|
111
|
-
The adapter now:
|
|
112
|
-
|
|
113
|
-
1. Uses `log_connection_yield` for all execution
|
|
114
|
-
2. Uses `raise_error` for all error handling
|
|
115
|
-
3. Uses declarative error classification (DATABASE_ERROR_REGEXPS)
|
|
116
|
-
4. Uses `synchronize` for all connection access
|
|
117
|
-
5. Overrides only what's different in SQL generation
|
|
118
|
-
6. Trusts Sequel's features - they're battle-tested and optimized
|
|
119
|
-
|
|
120
|
-
**Final Status: ~1282 lines total (53% reduction from original 2741 lines)**
|
|
121
|
-
|
|
122
|
-
This refactoring demonstrates the value of following established patterns and trusting framework conventions over reinventing the wheel.
|