sequel-duckdb 0.2.1 → 0.3.1

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/lib/sequel/adapters/duckdb.rb +3 -3
  4. data/lib/sequel/adapters/shared/duckdb.rb +17 -17
  5. data/lib/sequel/duckdb/version.rb +1 -1
  6. metadata +21 -60
  7. data/.beads/.beads-credential-key +0 -1
  8. data/.beads/.gitignore +0 -66
  9. data/.beads/README.md +0 -85
  10. data/.beads/config.yaml +0 -56
  11. data/.beads/hooks/post-checkout +0 -24
  12. data/.beads/hooks/post-merge +0 -24
  13. data/.beads/hooks/pre-commit +0 -24
  14. data/.beads/hooks/pre-push +0 -24
  15. data/.beads/hooks/prepare-commit-msg +0 -24
  16. data/.beads/metadata.json +0 -7
  17. data/.kiro/specs/advanced-sql-features-implementation/design.md +0 -26
  18. data/.kiro/specs/advanced-sql-features-implementation/requirements.md +0 -43
  19. data/.kiro/specs/advanced-sql-features-implementation/tasks.md +0 -28
  20. data/.kiro/specs/duckdb-sql-syntax-compatibility/design.md +0 -272
  21. data/.kiro/specs/duckdb-sql-syntax-compatibility/requirements.md +0 -84
  22. data/.kiro/specs/duckdb-sql-syntax-compatibility/tasks.md +0 -107
  23. data/.kiro/specs/edge-cases-and-validation-fixes/requirements.md +0 -32
  24. data/.kiro/specs/integration-test-database-setup/design.md +0 -0
  25. data/.kiro/specs/integration-test-database-setup/requirements.md +0 -117
  26. data/.kiro/specs/sequel-duckdb-adapter/design.md +0 -549
  27. data/.kiro/specs/sequel-duckdb-adapter/requirements.md +0 -202
  28. data/.kiro/specs/sequel-duckdb-adapter/tasks.md +0 -292
  29. data/.kiro/specs/sql-expression-handling-fix/design.md +0 -331
  30. data/.kiro/specs/sql-expression-handling-fix/requirements.md +0 -86
  31. data/.kiro/specs/sql-expression-handling-fix/tasks.md +0 -25
  32. data/.kiro/specs/test-infrastructure-improvements/requirements.md +0 -106
  33. data/.kiro/steering/product.md +0 -26
  34. data/.kiro/steering/structure.md +0 -88
  35. data/.kiro/steering/tech.md +0 -137
  36. data/.kiro/steering/testing.md +0 -213
  37. data/.mdformat.toml +0 -2
  38. data/.rubocop.yml +0 -161
  39. data/.rubocop_todo.yml +0 -323
  40. data/.yardopts +0 -8
  41. data/AGENTS.md +0 -180
  42. data/API_DOCUMENTATION.md +0 -943
  43. data/FINAL_STATUS.md +0 -99
  44. data/MIGRATION_EXAMPLES.md +0 -740
  45. data/PERFORMANCE_OPTIMIZATIONS.md +0 -726
  46. data/REFACTORING_SUMMARY.md +0 -264
  47. data/Rakefile +0 -43
  48. data/TASK_10.2_IMPLEMENTATION_SUMMARY.md +0 -182
  49. data/docs/DUCKDB_SQL_PATTERNS.md +0 -448
  50. data/docs/TASK_12_VERIFICATION_SUMMARY.md +0 -135
  51. data/justfile +0 -50
  52. data/plans/date_arithmetic.md +0 -420
  53. data/plans/engineering/Sequel.md +0 -471
  54. data/plans/engineering/duckdb.md +0 -712
  55. data/plans/engineering/sqlite.md +0 -453
  56. data/plans/mock_connection_bug.md +0 -333
  57. data/plans/mock_without_driver_gem.md +0 -371
  58. data/plans/over_engineering_analysis.md +0 -122
  59. data/plans/schema_management.md +0 -383
  60. data/sig/sequel/duckdb.rbs +0 -6
@@ -1,420 +0,0 @@
1
- # Date Arithmetic Extension Implementation Plan
2
-
3
- ## Overview
4
-
5
- Add support for Sequel's `date_arithmetic` extension to the DuckDB adapter, enabling database-independent date/timestamp interval arithmetic operations.
6
-
7
- ## Research Summary
8
-
9
- ### Sequel's date_arithmetic Extension
10
-
11
- The extension provides two primary methods:
12
-
13
- - `Sequel.date_add(expr, interval, opts)` - Adds interval to date/timestamp
14
- - `Sequel.date_sub(expr, interval, opts)` - Subtracts interval from date/timestamp
15
-
16
- **Supported interval units:**
17
-
18
- - `years`, `months`, `weeks`, `days`, `hours`, `minutes`, `seconds`
19
-
20
- **Input formats:**
21
-
22
- 1. Hash: `{years: 1, months: 2, days: 3}`
23
- 2. ActiveSupport::Duration: `1.year + 2.months + 3.days`
24
-
25
- **Key features:**
26
-
27
- - Database-independent API
28
- - Optional cast type override via `:cast` option
29
- - Weeks automatically converted to days (weeks × 7)
30
- - SQL injection protection (rejects String values)
31
- - Accumulates multiple values for same unit
32
-
33
- ### DuckDB's Interval Arithmetic
34
-
35
- **Native interval syntax:**
36
-
37
- ```sql
38
- -- Direct addition with INTERVAL keyword
39
- DATE '2024-01-15' + INTERVAL 1 YEAR
40
- DATE '2024-01-15' + INTERVAL 30 DAY
41
-
42
- -- Multiple interval components
43
- DATE '2024-01-15' + INTERVAL '1 year 2 months 3 days'
44
-
45
- -- Dynamic intervals with expressions
46
- DATE '2024-01-15' + INTERVAL (value) YEAR
47
- ```
48
-
49
- **Important characteristics:**
50
-
51
- 1. Adding INTERVAL to DATE returns TIMESTAMP (even for day-only intervals)
52
- 2. Three basis units: months, days, microseconds
53
- 3. Supports both unit keywords and string literals
54
- 4. Parentheses required for variable/expression values
55
-
56
- **Interval construction patterns:**
57
-
58
- - Single unit: `INTERVAL 5 DAY`
59
- - Multiple units in string: `INTERVAL '1 month 5 days 3 hours'`
60
- - Expression-based: `INTERVAL (column_value) MONTH`
61
-
62
- ## Implementation Strategy
63
-
64
- ### 1. Add DuckDB-specific date_add_sql_append Method
65
-
66
- **Location:** `lib/sequel/adapters/shared/duckdb.rb`
67
-
68
- **Approach:** Follow the pattern established in Sequel's date_arithmetic extension by adding a `date_add_sql_append` method to `DatabaseMethods` module.
69
-
70
- ### 2. Implementation Pattern
71
-
72
- The method should follow DuckDB's two supported approaches:
73
-
74
- #### Option A: Multiple INTERVAL additions (Recommended)
75
-
76
- Build expression by chaining interval additions:
77
-
78
- ```ruby
79
- # For date_add(:created_at, years: 1, months: 2, days: 5)
80
- # Generate: created_at + INTERVAL 1 YEAR + INTERVAL 2 MONTH + INTERVAL 5 DAY
81
- ```
82
-
83
- **Advantages:**
84
-
85
- - Simple, straightforward SQL generation
86
- - Handles dynamic/expression values naturally
87
- - Each interval component is explicit
88
- - Follows DuckDB's native syntax
89
-
90
- #### Option B: Single composite INTERVAL string
91
-
92
- Build a single interval string with multiple components:
93
-
94
- ```ruby
95
- # For date_add(:created_at, years: 1, months: 2, days: 5)
96
- # Generate: created_at + INTERVAL '1 years 2 months 5 days'
97
- ```
98
-
99
- **Challenges:**
100
-
101
- - Requires literal values only (no expressions)
102
- - More complex string building
103
- - Less flexible for dynamic intervals
104
-
105
- ### 3. Unit Mapping
106
-
107
- **Sequel → DuckDB unit mapping:**
108
-
109
- ```ruby
110
- DUCKDB_DURATION_UNITS = {
111
- years: 'YEAR',
112
- months: 'MONTH',
113
- days: 'DAY',
114
- hours: 'HOUR',
115
- minutes: 'MINUTE',
116
- seconds: 'SECOND'
117
- }.freeze
118
- ```
119
-
120
- Note: Weeks already converted to days by `DateAdd` initialization.
121
-
122
- ### 4. Handling Cast Types
123
-
124
- DuckDB returns TIMESTAMP when adding intervals to dates. Handle the `:cast` option:
125
-
126
- - If `cast_type` specified: wrap result in `CAST(... AS cast_type)`
127
- - Default cast_type: `Time` (TIMESTAMP)
128
- - For date-only results: user can specify `cast: :date`
129
-
130
- ### 5. Value Handling
131
-
132
- Support both literal values and SQL expressions:
133
-
134
- - Numeric literals: `1`, `2.5` → direct interpolation
135
- - SQL expressions: `Sequel.lit(...)`, column references → use parentheses
136
-
137
- ### 6. Implementation Code Structure
138
-
139
- **Important:** No registration needed in sequel-duckdb! Sequel's date_arithmetic extension already handles registration (line 253 of date_arithmetic.rb):
140
-
141
- ```ruby
142
- Dataset.register_extension(:date_arithmetic, SQL::DateAdd::DatasetMethods)
143
- ```
144
-
145
- The default implementation checks for adapter overrides via `if defined?(super)` (line 94-96), so the DuckDB adapter just needs to provide `date_add_sql_append` in its `DatasetMethods`, and Sequel will automatically call it.
146
-
147
- **Key Implementation Details:**
148
-
149
- 1. **DateAdd structure** (confirmed from source):
150
-
151
- - `da.expr` - the expression/column being added to
152
- - `da.interval` - Hash with symbol keys (e.g., `{hours: 5, minutes: -2}`)
153
- - `da.cast_type` - nil or a symbol (e.g., `:date`, `:timestamptz`)
154
-
155
- 2. **date_sub handling**: Sequel's `date_sub` automatically negates values before creating DateAdd:
156
-
157
- - Numeric values: negated directly (`hours: 5` → `hours: -5`)
158
- - Expressions: wrapped in negation (`Sequel::SQL::NumericExpression.new(:*, v, -1)`)
159
- - Adapter only needs to implement `date_add_sql_append` - negation is handled upstream
160
-
161
- 3. **Value types**:
162
-
163
- - `Numeric` (includes Integer, Float, BigDecimal) - can be used directly
164
- - Expressions (Sequel::LiteralString, Sequel::SQL::Expression, etc.) - need parentheses
165
-
166
- 4. **Sequel.lit with placeholders**: For SQL injection prevention, use array syntax:
167
-
168
- ```ruby
169
- Sequel.lit(["INTERVAL ", " HOUR"], value) # Safe - value is parameterized
170
- # NOT: Sequel.lit("INTERVAL #{value} HOUR") # Unsafe string interpolation
171
- ```
172
-
173
- ```ruby
174
- module Sequel
175
- module DuckDB
176
- module DatabaseMethods
177
- # ... existing methods ...
178
- end
179
-
180
- # Dataset methods for DuckDB-specific SQL generation
181
- # These methods will be mixed into DuckDB::Database datasets
182
- module DatasetMethods
183
- DUCKDB_DURATION_UNITS = {
184
- years: 'YEAR',
185
- months: 'MONTH',
186
- days: 'DAY',
187
- hours: 'HOUR',
188
- minutes: 'MINUTE',
189
- seconds: 'SECOND'
190
- }.freeze
191
-
192
- # DuckDB-specific implementation of date arithmetic
193
- # This will be called by Sequel's date_arithmetic extension
194
- # via the `super` mechanism when the extension is loaded
195
- def date_add_sql_append(sql, da)
196
- expr = da.expr
197
- interval_hash = da.interval
198
- cast_type = da.cast_type
199
-
200
- # Build expression with chained interval additions
201
- result = expr
202
- interval_hash.each do |unit, value|
203
- sql_unit = DUCKDB_DURATION_UNITS[unit]
204
- next unless sql_unit
205
-
206
- # Create interval addition
207
- interval = build_interval_literal(value, sql_unit)
208
- result = Sequel.+(result, interval)
209
- end
210
-
211
- # Apply cast if specified or default to Time (TIMESTAMP)
212
- # Note: DuckDB returns TIMESTAMP when adding intervals to DATE
213
- result = Sequel.cast(result, cast_type || Time)
214
-
215
- literal_append(sql, result)
216
- end
217
-
218
- private
219
-
220
- def build_interval_literal(value, unit)
221
- # If value is numeric, use direct syntax with placeholder
222
- # If value is expression, wrap in parentheses with placeholder
223
- if value.is_a?(Numeric)
224
- # Direct numeric: INTERVAL 5 HOUR
225
- Sequel.lit(["INTERVAL ", " ", ""], value, unit)
226
- else
227
- # Expression: INTERVAL (column_name) HOUR
228
- # Note: expressions already include negation from date_sub
229
- Sequel.lit(["INTERVAL (", ") ", ""], value, unit)
230
- end
231
- end
232
- end
233
- end
234
-
235
- # Make DatasetMethods available to DuckDB datasets
236
- # This allows the date_add_sql_append override to be found
237
- DuckDB::Database.dataset_module(DuckDB::DatasetMethods)
238
- end
239
- ```
240
-
241
- ## Testing Strategy
242
-
243
- ### Unit Tests Required
244
-
245
- 1. **Basic addition/subtraction:**
246
-
247
- - Single unit intervals (years, months, days, hours, minutes, seconds)
248
- - Multiple unit intervals combined
249
- - Verify weeks → days conversion
250
-
251
- 2. **Value types:**
252
-
253
- - Numeric literals
254
- - SQL expressions (column references, calculations)
255
- - Zero values (should be skipped)
256
-
257
- 3. **Cast handling:**
258
-
259
- - Default cast to timestamp
260
- - Explicit cast to date
261
- - Explicit cast to timestamptz
262
-
263
- 4. **ActiveSupport::Duration support:**
264
-
265
- - Verify `1.year + 2.months` syntax works
266
- - Mixed duration objects
267
-
268
- 5. **Edge cases:**
269
-
270
- - Empty intervals
271
- - Negative values (via date_sub)
272
- - Large values
273
-
274
- ### Integration Tests
275
-
276
- Test against actual DuckDB database:
277
-
278
- ```ruby
279
- DB.extension :date_arithmetic
280
-
281
- # Test basic addition
282
- result = DB[:events]
283
- .select(Sequel.date_add(:created_at, days: 5).as(:future_date))
284
- .first
285
-
286
- # Test subtraction
287
- result = DB[:events]
288
- .where(Sequel.date_sub(:expires_at, hours: 24) < Sequel::CURRENT_TIMESTAMP)
289
- .all
290
-
291
- # Test complex intervals
292
- result = DB[:events]
293
- .select(Sequel.date_add(:start_date, {years: 1, months: 2, days: 15}).as(:end_date))
294
- .first
295
-
296
- # Test with cast option
297
- result = DB[:events]
298
- .select(Sequel.date_add(:date_only, {days: 7}, cast: :date).as(:next_week))
299
- .first
300
- ```
301
-
302
- ## SQL Output Examples
303
-
304
- **Input:**
305
-
306
- ```ruby
307
- Sequel.date_add(:created_at, years: 1, months: 2, days: 5)
308
- ```
309
-
310
- **Generated SQL:**
311
-
312
- ```sql
313
- CAST(created_at + INTERVAL 1 YEAR + INTERVAL 2 MONTH + INTERVAL 5 DAY AS TIMESTAMP)
314
- ```
315
-
316
- **Input:**
317
-
318
- ```ruby
319
- Sequel.date_sub(:expires_at, hours: 12, minutes: 30)
320
- ```
321
-
322
- **Generated SQL:**
323
-
324
- ```sql
325
- CAST(expires_at + INTERVAL -12 HOUR + INTERVAL -30 MINUTE AS TIMESTAMP)
326
- ```
327
-
328
- **Input:**
329
-
330
- ```ruby
331
- Sequel.date_add(:start_date, {days: :duration_column}, cast: :date)
332
- ```
333
-
334
- **Generated SQL:**
335
-
336
- ```sql
337
- CAST(start_date + INTERVAL (duration_column) DAY AS DATE)
338
- ```
339
-
340
- ## Files to Modify
341
-
342
- 1. **`lib/sequel/adapters/shared/duckdb.rb`**
343
-
344
- - Add `DatasetMethods` module with `date_add_sql_append`
345
- - Add `DUCKDB_DURATION_UNITS` constant
346
- - Add `build_interval_literal` helper method
347
- - Use `DuckDB::Database.dataset_module(DuckDB::DatasetMethods)` to make methods available
348
- - NO registration needed - Sequel's extension handles it
349
-
350
- 2. **`spec/sequel/adapters/date_arithmetic_spec.rb`** (create new)
351
-
352
- - Unit tests for date_add_sql_append
353
- - Integration tests with actual database
354
- - Edge case coverage
355
-
356
- 3. **`README.md`** (optional documentation)
357
-
358
- - Add date_arithmetic to supported extensions list
359
- - Include usage examples
360
-
361
- ## Potential Issues & Considerations
362
-
363
- 1. **Type casting behavior:**
364
-
365
- - DuckDB returns TIMESTAMP when adding intervals to DATE
366
- - Users expecting DATE output need explicit `cast: :date`
367
- - Document this behavior clearly
368
-
369
- 2. **Expression vs literal handling:**
370
-
371
- - Literals can be used directly: `INTERVAL 5 DAY`
372
- - Expressions need parentheses: `INTERVAL (column) DAY`
373
- - Distinguish between Numeric and other values
374
-
375
- 3. **Interval order:**
376
-
377
- - Hash iteration order in Ruby 1.9+ is insertion order
378
- - DateAdd initializes with `Hash.new(0)` then converts to Hash
379
- - Order shouldn't matter for interval addition (commutative)
380
-
381
- 4. **PostgreSQL compatibility:**
382
-
383
- - DuckDB's interval syntax is similar to PostgreSQL
384
- - Can reference PostgreSQL implementation for patterns
385
- - Differences: DuckDB doesn't have make_interval function
386
-
387
- 5. **Extension loading:**
388
-
389
- - Extension must be loaded at Database level: `DB.extension :date_arithmetic`
390
- - Sequel's extension registers its `DatasetMethods` automatically
391
- - The `if defined?(super)` check in Sequel's implementation will call DuckDB's override
392
- - No registration needed in sequel-duckdb - just provide the override method
393
-
394
- ## Implementation Checklist
395
-
396
- - [ ] Create `DatasetMethods` module in shared/duckdb.rb
397
- - [ ] Implement `date_add_sql_append` method
398
- - [ ] Add `DUCKDB_DURATION_UNITS` constant mapping
399
- - [ ] Implement `build_interval_literal` helper
400
- - [ ] Use `dataset_module` to make methods available (NO explicit registration needed)
401
- - [ ] Write comprehensive specs
402
- - [ ] Test with literal numeric values
403
- - [ ] Test with SQL expression values
404
- - [ ] Test cast option handling
405
- - [ ] Test ActiveSupport::Duration integration
406
- - [ ] Test date_sub (negative intervals)
407
- - [ ] Verify all 6 interval units work
408
- - [ ] Document any DuckDB-specific behaviors
409
- - [ ] Update README if needed
410
-
411
- ## Success Criteria
412
-
413
- 1. All Sequel date_arithmetic API methods work correctly
414
- 2. Both numeric literals and SQL expressions supported as interval values
415
- 3. Cast option properly controls output type
416
- 4. ActiveSupport::Duration objects handled correctly
417
- 5. Generated SQL is clean and efficient
418
- 6. No SQL injection vulnerabilities
419
- 7. Comprehensive test coverage (>95%)
420
- 8. Consistent with other DuckDB adapter patterns