sequel-duckdb 0.2.1 → 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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/lib/sequel/duckdb/version.rb +1 -1
  4. metadata +3 -57
  5. data/.beads/.beads-credential-key +0 -1
  6. data/.beads/.gitignore +0 -66
  7. data/.beads/README.md +0 -85
  8. data/.beads/config.yaml +0 -56
  9. data/.beads/hooks/post-checkout +0 -24
  10. data/.beads/hooks/post-merge +0 -24
  11. data/.beads/hooks/pre-commit +0 -24
  12. data/.beads/hooks/pre-push +0 -24
  13. data/.beads/hooks/prepare-commit-msg +0 -24
  14. data/.beads/metadata.json +0 -7
  15. data/.kiro/specs/advanced-sql-features-implementation/design.md +0 -26
  16. data/.kiro/specs/advanced-sql-features-implementation/requirements.md +0 -43
  17. data/.kiro/specs/advanced-sql-features-implementation/tasks.md +0 -28
  18. data/.kiro/specs/duckdb-sql-syntax-compatibility/design.md +0 -272
  19. data/.kiro/specs/duckdb-sql-syntax-compatibility/requirements.md +0 -84
  20. data/.kiro/specs/duckdb-sql-syntax-compatibility/tasks.md +0 -107
  21. data/.kiro/specs/edge-cases-and-validation-fixes/requirements.md +0 -32
  22. data/.kiro/specs/integration-test-database-setup/design.md +0 -0
  23. data/.kiro/specs/integration-test-database-setup/requirements.md +0 -117
  24. data/.kiro/specs/sequel-duckdb-adapter/design.md +0 -549
  25. data/.kiro/specs/sequel-duckdb-adapter/requirements.md +0 -202
  26. data/.kiro/specs/sequel-duckdb-adapter/tasks.md +0 -292
  27. data/.kiro/specs/sql-expression-handling-fix/design.md +0 -331
  28. data/.kiro/specs/sql-expression-handling-fix/requirements.md +0 -86
  29. data/.kiro/specs/sql-expression-handling-fix/tasks.md +0 -25
  30. data/.kiro/specs/test-infrastructure-improvements/requirements.md +0 -106
  31. data/.kiro/steering/product.md +0 -26
  32. data/.kiro/steering/structure.md +0 -88
  33. data/.kiro/steering/tech.md +0 -137
  34. data/.kiro/steering/testing.md +0 -213
  35. data/.mdformat.toml +0 -2
  36. data/.rubocop.yml +0 -161
  37. data/.rubocop_todo.yml +0 -323
  38. data/.yardopts +0 -8
  39. data/AGENTS.md +0 -180
  40. data/API_DOCUMENTATION.md +0 -943
  41. data/FINAL_STATUS.md +0 -99
  42. data/MIGRATION_EXAMPLES.md +0 -740
  43. data/PERFORMANCE_OPTIMIZATIONS.md +0 -726
  44. data/REFACTORING_SUMMARY.md +0 -264
  45. data/Rakefile +0 -43
  46. data/TASK_10.2_IMPLEMENTATION_SUMMARY.md +0 -182
  47. data/docs/DUCKDB_SQL_PATTERNS.md +0 -448
  48. data/docs/TASK_12_VERIFICATION_SUMMARY.md +0 -135
  49. data/justfile +0 -50
  50. data/plans/date_arithmetic.md +0 -420
  51. data/plans/engineering/Sequel.md +0 -471
  52. data/plans/engineering/duckdb.md +0 -712
  53. data/plans/engineering/sqlite.md +0 -453
  54. data/plans/mock_connection_bug.md +0 -333
  55. data/plans/mock_without_driver_gem.md +0 -371
  56. data/plans/over_engineering_analysis.md +0 -122
  57. data/plans/schema_management.md +0 -383
  58. data/sig/sequel/duckdb.rbs +0 -6
data/API_DOCUMENTATION.md DELETED
@@ -1,943 +0,0 @@
1
- # API Documentation - Sequel DuckDB Adapter
2
-
3
- This document provides comprehensive API documentation for the Sequel DuckDB adapter, including all public methods, configuration options, and usage patterns.
4
-
5
- ## Table of Contents
6
-
7
- 1. [Version Compatibility](#version-compatibility)
8
- 2. [Database Class API](#database-class-api)
9
- 3. [Dataset Class API](#dataset-class-api)
10
- 4. [SQL Generation Patterns](#sql-generation-patterns)
11
- 5. [Configuration Options](#configuration-options)
12
- 6. [Error Handling](#error-handling)
13
- 7. [Data Type Mappings](#data-type-mappings)
14
- 8. [Performance Tuning](#performance-tuning)
15
-
16
- ## Version Compatibility
17
-
18
- ### Supported Versions
19
-
20
- | Component | Minimum Version | Recommended Version | Notes |
21
- | ----------- | --------------- | ------------------- | ------------------------------------------ |
22
- | Ruby | 3.1.0 | 3.2.0+ | Required for modern syntax and performance |
23
- | Sequel | 5.0.0 | 5.70.0+ | Core ORM functionality |
24
- | DuckDB | 0.8.0 | 0.9.0+ | Database engine |
25
- | ruby-duckdb | 1.0.0 | 1.0.0+ | Ruby client library |
26
-
27
- ### Ruby Version Support
28
-
29
- - **Ruby 3.1.0+**: Full support with all features
30
- - **Ruby 3.2.0+**: Recommended for best performance
31
- - **Ruby 3.3.0+**: Latest features and optimizations
32
-
33
- ### Sequel Version Support
34
-
35
- - **Sequel 5.0+**: Basic functionality
36
- - **Sequel 5.50+**: Enhanced schema introspection
37
- - **Sequel 5.70+**: Full feature compatibility
38
-
39
- ### DuckDB Version Support
40
-
41
- - **DuckDB 0.8.0+**: Core functionality
42
- - **DuckDB 0.9.0+**: Enhanced JSON and array support
43
- - **DuckDB 0.10.0+**: Latest analytical features
44
-
45
- ## Database Class API
46
-
47
- ### Connection Methods
48
-
49
- #### `Sequel.connect(connection_string)`
50
-
51
- Connect to a DuckDB database using a connection string.
52
-
53
- ```ruby
54
- # In-memory database
55
- db = Sequel.connect('duckdb::memory:')
56
-
57
- # File database
58
- db = Sequel.connect('duckdb:///path/to/database.duckdb')
59
- db = Sequel.connect('duckdb://relative/path/to/database.duckdb')
60
-
61
- # With query parameters
62
- db = Sequel.connect('duckdb:///path/to/database.duckdb?readonly=true')
63
- ```
64
-
65
- **Parameters:**
66
-
67
- - `connection_string` (String): DuckDB connection string
68
-
69
- **Returns:** `Sequel::DuckDB::Database` instance
70
-
71
- **Raises:** `Sequel::DatabaseConnectionError` if connection fails
72
-
73
- #### `Sequel.connect(options_hash)`
74
-
75
- Connect using a configuration hash.
76
-
77
- ```ruby
78
- db = Sequel.connect(
79
- adapter: 'duckdb',
80
- database: '/path/to/database.duckdb',
81
- readonly: false,
82
- config: {
83
- memory_limit: '4GB',
84
- threads: 8,
85
- temp_directory: '/tmp/duckdb'
86
- }
87
- )
88
- ```
89
-
90
- **Parameters:**
91
-
92
- - `options_hash` (Hash): Configuration options
93
- - `:adapter` (String): Must be 'duckdb'
94
- - `:database` (String): Database path or ':memory:'
95
- - `:readonly` (Boolean): Read-only mode (default: false)
96
- - `:config` (Hash): DuckDB-specific configuration
97
-
98
- **Returns:** `Sequel::DuckDB::Database` instance
99
-
100
- ### Schema Introspection Methods
101
-
102
- #### `#tables(options = {})`
103
-
104
- Get list of all tables in the database.
105
-
106
- ```ruby
107
- db.tables
108
- # => [:users, :products, :orders]
109
-
110
- # With schema specification
111
- db.tables(schema: 'main')
112
- # => [:users, :products, :orders]
113
- ```
114
-
115
- **Parameters:**
116
-
117
- - `options` (Hash): Optional parameters
118
- - `:schema` (String): Schema name (default: 'main')
119
-
120
- **Returns:** Array of table names as symbols
121
-
122
- #### `#schema(table_name, options = {})`
123
-
124
- Get detailed schema information for a table.
125
-
126
- ```ruby
127
- db.schema(:users)
128
- # => [
129
- # [:id, {type: :integer, db_type: "INTEGER", primary_key: true, allow_null: false}],
130
- # [:name, {type: :string, db_type: "VARCHAR", primary_key: false, allow_null: false}],
131
- # [:email, {type: :string, db_type: "VARCHAR", primary_key: false, allow_null: true}]
132
- # ]
133
- ```
134
-
135
- **Parameters:**
136
-
137
- - `table_name` (Symbol/String): Name of the table
138
- - `options` (Hash): Optional parameters
139
- - `:schema` (String): Schema name (default: 'main')
140
-
141
- **Returns:** Array of `[column_name, column_info]` pairs
142
-
143
- **Column Info Hash:**
144
-
145
- - `:type` (Symbol): Sequel type (:integer, :string, :boolean, etc.)
146
- - `:db_type` (String): DuckDB native type
147
- - `:primary_key` (Boolean): Whether column is part of primary key
148
- - `:allow_null` (Boolean): Whether column allows NULL values
149
- - `:default` (Object): Default value or nil
150
- - `:max_length` (Integer): Maximum length for string types
151
- - `:precision` (Integer): Precision for numeric types
152
- - `:scale` (Integer): Scale for decimal types
153
-
154
- #### `#indexes(table_name, options = {})`
155
-
156
- Get index information for a table.
157
-
158
- ```ruby
159
- db.indexes(:users)
160
- # => {
161
- # :users_email_index => {
162
- # columns: [:email],
163
- # unique: true,
164
- # primary: false
165
- # }
166
- # }
167
- ```
168
-
169
- **Parameters:**
170
-
171
- - `table_name` (Symbol/String): Name of the table
172
- - `options` (Hash): Optional parameters
173
-
174
- **Returns:** Hash of `index_name => index_info`
175
-
176
- #### `#table_exists?(table_name, options = {})`
177
-
178
- Check if a table exists.
179
-
180
- ```ruby
181
- db.table_exists?(:users) # => true
182
- db.table_exists?(:nonexistent) # => false
183
- ```
184
-
185
- **Parameters:**
186
-
187
- - `table_name` (Symbol/String): Name of the table
188
- - `options` (Hash): Optional parameters
189
-
190
- **Returns:** Boolean
191
-
192
- ### SQL Execution Methods
193
-
194
- #### `#execute(sql, options = {})`
195
-
196
- Execute raw SQL statement.
197
-
198
- ```ruby
199
- # Simple query
200
- result = db.execute("SELECT COUNT(*) FROM users")
201
-
202
- # With parameters
203
- result = db.execute("SELECT * FROM users WHERE age > ?", [25])
204
-
205
- # With block for result processing
206
- db.execute("SELECT * FROM users") do |row|
207
- puts row[:name]
208
- end
209
- ```
210
-
211
- **Parameters:**
212
-
213
- - `sql` (String): SQL statement to execute
214
- - `options` (Hash/Array): Parameters or options
215
- - If Array: Parameters for prepared statement
216
- - If Hash: Options including `:params` key
217
-
218
- **Returns:** Query result or number of affected rows
219
-
220
- #### `#execute_insert(sql, options = {})`
221
-
222
- Execute INSERT statement.
223
-
224
- ```ruby
225
- db.execute_insert("INSERT INTO users (name, email) VALUES (?, ?)", ['John', 'john@example.com'])
226
- ```
227
-
228
- **Parameters:**
229
-
230
- - `sql` (String): INSERT SQL statement
231
- - `options` (Hash): Options for execution
232
-
233
- **Returns:** Inserted record ID (if available) or nil
234
-
235
- #### `#execute_update(sql, options = {})`
236
-
237
- Execute UPDATE statement.
238
-
239
- ```ruby
240
- affected_rows = db.execute_update("UPDATE users SET active = ? WHERE age > ?", [true, 25])
241
- ```
242
-
243
- **Parameters:**
244
-
245
- - `sql` (String): UPDATE SQL statement
246
- - `options` (Hash): Options for execution
247
-
248
- **Returns:** Number of affected rows
249
-
250
- ### Transaction Methods
251
-
252
- #### `#transaction(options = {}, &block)`
253
-
254
- Execute a block within a database transaction.
255
-
256
- ```ruby
257
- # Basic transaction
258
- db.transaction do
259
- db[:users].insert(name: 'Alice', email: 'alice@example.com')
260
- db[:profiles].insert(user_id: db[:users].max(:id), bio: 'Developer')
261
- end
262
-
263
- # With rollback
264
- db.transaction do
265
- db[:users].insert(name: 'Bob', email: 'bob@example.com')
266
- raise Sequel::Rollback if some_condition
267
- end
268
-
269
- # With savepoint (nested transaction)
270
- db.transaction do
271
- db[:users].insert(name: 'Charlie', email: 'charlie@example.com')
272
-
273
- db.transaction(savepoint: true) do
274
- # This can be rolled back independently
275
- db[:audit_log].insert(action: 'user_created')
276
- end
277
- end
278
- ```
279
-
280
- **Parameters:**
281
-
282
- - `options` (Hash): Transaction options
283
- - `:savepoint` (Boolean): Use savepoint for nested transaction
284
- - `:isolation` (Symbol): Transaction isolation level
285
- - `:server` (Symbol): Server/connection to use
286
-
287
- **Returns:** Result of the block
288
-
289
- **Raises:**
290
-
291
- - `Sequel::Rollback`: To rollback transaction
292
- - `Sequel::DatabaseError`: On transaction errors
293
-
294
- ### Connection Management
295
-
296
- #### `#disconnect`
297
-
298
- Close all database connections.
299
-
300
- ```ruby
301
- db.disconnect
302
- ```
303
-
304
- #### `#test_connection`
305
-
306
- Test if the database connection is working.
307
-
308
- ```ruby
309
- db.test_connection # => true
310
- ```
311
-
312
- **Returns:** Boolean indicating connection status
313
-
314
- ## Dataset Class API
315
-
316
- ### Query Building Methods
317
-
318
- #### `#where(conditions)`
319
-
320
- Add WHERE clause to query.
321
-
322
- ```ruby
323
- users = db[:users]
324
-
325
- # Hash conditions
326
- users.where(active: true, age: 25)
327
-
328
- # Block conditions
329
- users.where { age > 25 }
330
-
331
- # String conditions with parameters
332
- users.where("name LIKE ?", 'John%')
333
-
334
- # Complex conditions
335
- users.where(Sequel.like(:name, 'John%') & (Sequel[:age] > 25))
336
- ```
337
-
338
- **Parameters:**
339
-
340
- - `conditions`: Various condition formats (Hash, String, Block, Sequel expressions)
341
-
342
- **Returns:** New Dataset with WHERE clause added
343
-
344
- #### `#select(*columns)`
345
-
346
- Specify columns to select.
347
-
348
- ```ruby
349
- users = db[:users]
350
-
351
- # Select specific columns
352
- users.select(:id, :name, :email)
353
-
354
- # Select with aliases
355
- users.select(:id, Sequel[:name].as(:full_name))
356
-
357
- # Select with functions
358
- users.select(:id, Sequel.function(:upper, :name).as(:name_upper))
359
- ```
360
-
361
- **Parameters:**
362
-
363
- - `columns`: Column names, expressions, or functions
364
-
365
- **Returns:** New Dataset with SELECT clause
366
-
367
- #### `#order(*columns)`
368
-
369
- Add ORDER BY clause.
370
-
371
- ```ruby
372
- users = db[:users]
373
-
374
- # Simple ordering
375
- users.order(:name)
376
-
377
- # Multiple columns
378
- users.order(:name, :created_at)
379
-
380
- # Descending order
381
- users.order(Sequel.desc(:created_at))
382
-
383
- # Mixed ordering
384
- users.order(:name, Sequel.desc(:created_at))
385
- ```
386
-
387
- **Parameters:**
388
-
389
- - `columns`: Column names or ordering expressions
390
-
391
- **Returns:** New Dataset with ORDER BY clause
392
-
393
- #### `#limit(count, offset = nil)`
394
-
395
- Add LIMIT and optional OFFSET.
396
-
397
- ```ruby
398
- users = db[:users]
399
-
400
- # Limit only
401
- users.limit(10)
402
-
403
- # Limit with offset
404
- users.limit(10, 20)
405
-
406
- # Pagination helper
407
- users.paginate(page: 2, per_page: 10)
408
- ```
409
-
410
- **Parameters:**
411
-
412
- - `count` (Integer): Maximum number of rows
413
- - `offset` (Integer): Number of rows to skip
414
-
415
- **Returns:** New Dataset with LIMIT clause
416
-
417
- #### `#group(*columns)`
418
-
419
- Add GROUP BY clause.
420
-
421
- ```ruby
422
- orders = db[:orders]
423
-
424
- # Group by single column
425
- orders.group(:status)
426
-
427
- # Group by multiple columns
428
- orders.group(:status, :user_id)
429
-
430
- # With aggregation
431
- orders.group(:status).select(:status, Sequel.count(:id).as(:count))
432
- ```
433
-
434
- **Parameters:**
435
-
436
- - `columns`: Column names to group by
437
-
438
- **Returns:** New Dataset with GROUP BY clause
439
-
440
- #### `#having(conditions)`
441
-
442
- Add HAVING clause (used with GROUP BY).
443
-
444
- ```ruby
445
- orders = db[:orders]
446
-
447
- orders.group(:user_id)
448
- .select(:user_id, Sequel.sum(:total).as(:total_spent))
449
- .having { sum(:total) > 1000 }
450
- ```
451
-
452
- **Parameters:**
453
-
454
- - `conditions`: Conditions for HAVING clause
455
-
456
- **Returns:** New Dataset with HAVING clause
457
-
458
- ### Join Methods
459
-
460
- #### `#join(table, conditions = nil, options = {})`
461
-
462
- Add INNER JOIN.
463
-
464
- ```ruby
465
- users = db[:users]
466
-
467
- # Simple join
468
- users.join(:orders, user_id: :id)
469
-
470
- # Join with table aliases
471
- users.join(:orders___o, user_id: :id)
472
-
473
- # Complex join conditions
474
- users.join(:orders, Sequel[:orders][:user_id] => Sequel[:users][:id])
475
- ```
476
-
477
- **Parameters:**
478
-
479
- - `table`: Table to join (Symbol/String)
480
- - `conditions`: Join conditions (Hash or Sequel expression)
481
- - `options`: Join options
482
-
483
- **Returns:** New Dataset with JOIN clause
484
-
485
- #### `#left_join(table, conditions = nil, options = {})`
486
-
487
- Add LEFT OUTER JOIN.
488
-
489
- ```ruby
490
- users.left_join(:profiles, user_id: :id)
491
- ```
492
-
493
- #### `#right_join(table, conditions = nil, options = {})`
494
-
495
- Add RIGHT OUTER JOIN.
496
-
497
- ```ruby
498
- users.right_join(:orders, user_id: :id)
499
- ```
500
-
501
- #### `#full_join(table, conditions = nil, options = {})`
502
-
503
- Add FULL OUTER JOIN.
504
-
505
- ```ruby
506
- users.full_join(:profiles, user_id: :id)
507
- ```
508
-
509
- ### Data Retrieval Methods
510
-
511
- #### `#all`
512
-
513
- Retrieve all matching records.
514
-
515
- ```ruby
516
- users = db[:users].where(active: true).all
517
- # => [{id: 1, name: 'John', ...}, {id: 2, name: 'Jane', ...}]
518
- ```
519
-
520
- **Returns:** Array of record hashes
521
-
522
- #### `#first`
523
-
524
- Retrieve first matching record.
525
-
526
- ```ruby
527
- user = db[:users].where(email: 'john@example.com').first
528
- # => {id: 1, name: 'John', email: 'john@example.com', ...}
529
- ```
530
-
531
- **Returns:** Record hash or nil if not found
532
-
533
- #### `#last`
534
-
535
- Retrieve last matching record (requires ORDER BY).
536
-
537
- ```ruby
538
- user = db[:users].order(:created_at).last
539
- ```
540
-
541
- **Returns:** Record hash or nil if not found
542
-
543
- #### `#count`
544
-
545
- Count matching records.
546
-
547
- ```ruby
548
- count = db[:users].where(active: true).count
549
- # => 42
550
- ```
551
-
552
- **Returns:** Integer count
553
-
554
- #### `#each(&block)`
555
-
556
- Iterate over all matching records.
557
-
558
- ```ruby
559
- db[:users].where(active: true).each do |user|
560
- puts user[:name]
561
- end
562
- ```
563
-
564
- **Parameters:**
565
-
566
- - `block`: Block to execute for each record
567
-
568
- **Returns:** Dataset (for chaining)
569
-
570
- #### `#paged_each(options = {}, &block)`
571
-
572
- Iterate over records in batches for memory efficiency.
573
-
574
- ```ruby
575
- db[:large_table].paged_each(rows_per_fetch: 1000) do |row|
576
- process_row(row)
577
- end
578
- ```
579
-
580
- **Parameters:**
581
-
582
- - `options` (Hash): Paging options
583
- - `:rows_per_fetch` (Integer): Batch size (default: 1000)
584
- - `block`: Block to execute for each record
585
-
586
- ### Data Modification Methods
587
-
588
- #### `#insert(values)`
589
-
590
- Insert a single record.
591
-
592
- ```ruby
593
- user_id = db[:users].insert(
594
- name: 'John Doe',
595
- email: 'john@example.com',
596
- created_at: Time.now
597
- )
598
- ```
599
-
600
- **Parameters:**
601
-
602
- - `values` (Hash): Column values to insert
603
-
604
- **Returns:** Inserted record ID (if available)
605
-
606
- #### `#multi_insert(array)`
607
-
608
- Insert multiple records efficiently.
609
-
610
- ```ruby
611
- db[:users].multi_insert([
612
- {name: 'Alice', email: 'alice@example.com'},
613
- {name: 'Bob', email: 'bob@example.com'},
614
- {name: 'Charlie', email: 'charlie@example.com'}
615
- ])
616
- ```
617
-
618
- **Parameters:**
619
-
620
- - `array` (Array): Array of record hashes
621
-
622
- **Returns:** Number of inserted records
623
-
624
- #### `#update(values)`
625
-
626
- Update matching records.
627
-
628
- ```ruby
629
- affected_rows = db[:users]
630
- .where(active: false)
631
- .update(active: true, updated_at: Time.now)
632
- ```
633
-
634
- **Parameters:**
635
-
636
- - `values` (Hash): Column values to update
637
-
638
- **Returns:** Number of affected rows
639
-
640
- #### `#delete`
641
-
642
- Delete matching records.
643
-
644
- ```ruby
645
- deleted_count = db[:users].where { created_at < Date.today - 365 }.delete
646
- ```
647
-
648
- **Returns:** Number of deleted rows
649
-
650
- ### Analytical Methods (DuckDB-Specific)
651
-
652
- #### Window Functions
653
-
654
- ```ruby
655
- # Ranking within groups
656
- db[:sales].select(
657
- :product_id,
658
- :amount,
659
- Sequel.function(:rank).over(
660
- partition: :category_id,
661
- order: Sequel.desc(:amount)
662
- ).as(:rank)
663
- )
664
-
665
- # Running totals
666
- db[:sales].select(
667
- :date,
668
- :amount,
669
- Sequel.function(:sum, :amount).over(
670
- order: :date
671
- ).as(:running_total)
672
- )
673
- ```
674
-
675
- #### Common Table Expressions (CTEs)
676
-
677
- ```ruby
678
- # Simple CTE
679
- db.with(:high_spenders,
680
- db[:orders].group(:user_id).having { sum(:total) > 1000 }.select(:user_id)
681
- ).from(:high_spenders).join(:users, id: :user_id)
682
-
683
- # Recursive CTE
684
- db.with_recursive(:category_tree,
685
- db[:categories].where(parent_id: nil),
686
- db[:categories].join(:category_tree, parent_id: :id)
687
- ).from(:category_tree)
688
- ```
689
-
690
- ## SQL Generation Patterns
691
-
692
- The sequel-duckdb adapter generates SQL optimized for DuckDB's analytical capabilities while maintaining compatibility with Sequel conventions. Understanding these patterns helps developers write efficient queries and troubleshoot issues.
693
-
694
- ### Key SQL Pattern Features
695
-
696
- - **Clean LIKE clauses** without unnecessary ESCAPE clauses
697
- - **ILIKE conversion** to UPPER() LIKE UPPER() for case-insensitive matching
698
- - **Regex support** using DuckDB's regexp_matches() function
699
- - **Qualified column references** using standard dot notation
700
- - **Automatic recursive CTE detection** for WITH RECURSIVE syntax
701
- - **Proper expression parentheses** for correct operator precedence
702
-
703
- ### Quick Reference
704
-
705
- ```ruby
706
- # LIKE patterns (clean syntax)
707
- dataset.where(Sequel.like(:name, "%John%"))
708
- # SQL: SELECT * FROM users WHERE (name LIKE '%John%')
709
-
710
- # ILIKE patterns (case-insensitive)
711
- dataset.where(Sequel.ilike(:name, "%john%"))
712
- # SQL: SELECT * FROM users WHERE (UPPER(name) LIKE UPPER('%john%'))
713
-
714
- # Regex patterns
715
- dataset.where(name: /^John/)
716
- # SQL: SELECT * FROM users WHERE (regexp_matches(name, '^John'))
717
-
718
- # Qualified column references
719
- dataset.join(:profiles, user_id: :id)
720
- # SQL: SELECT * FROM users INNER JOIN profiles ON (profiles.user_id = users.id)
721
-
722
- # Recursive CTEs (auto-detected)
723
- base_case = db.select(Sequel.as(1, :n))
724
- recursive_case = db[:t].select(Sequel.lit("n + 1")).where { n < 10 }
725
- combined = base_case.union(recursive_case, all: true)
726
- dataset.with(:t, combined).from(:t)
727
- # SQL: WITH RECURSIVE t AS (SELECT 1 AS n UNION ALL SELECT n + 1 FROM t WHERE (n < 10)) SELECT * FROM t
728
- ```
729
-
730
- ### Detailed Documentation
731
-
732
- For comprehensive documentation of all SQL patterns, including design decisions and troubleshooting tips, see [DUCKDB_SQL_PATTERNS.md](DUCKDB_SQL_PATTERNS.md).
733
-
734
- ## Configuration Options
735
-
736
- ### Database Configuration
737
-
738
- ```ruby
739
- db = Sequel.connect(
740
- adapter: 'duckdb',
741
- database: '/path/to/database.duckdb',
742
-
743
- # Connection options
744
- readonly: false,
745
-
746
- # DuckDB-specific configuration
747
- config: {
748
- # Memory management
749
- memory_limit: '4GB', # Maximum memory usage
750
- max_memory: '8GB', # Memory limit before spilling to disk
751
- temp_directory: '/tmp/duckdb', # Temporary file location
752
-
753
- # Performance tuning
754
- threads: 8, # Number of threads for parallel processing
755
- enable_optimizer: true, # Enable query optimizer
756
- enable_profiling: false, # Enable query profiling
757
-
758
- # Behavioral settings
759
- default_order: 'ASC', # Default sort order
760
- preserve_insertion_order: false, # Preserve insertion order
761
-
762
- # Extension settings
763
- autoload_known_extensions: true, # Auto-load known extensions
764
- autoinstall_known_extensions: false # Auto-install extensions
765
- },
766
-
767
- # Sequel connection pool options
768
- max_connections: 10, # Connection pool size
769
- pool_timeout: 5, # Connection timeout in seconds
770
- pool_sleep_time: 0.001, # Sleep time between connection retries
771
- pool_connection_validation: true # Validate connections before use
772
- )
773
- ```
774
-
775
- ### Runtime Configuration
776
-
777
- ```ruby
778
- # Change settings at runtime
779
- db.run "SET memory_limit='2GB'"
780
- db.run "SET threads=4"
781
- db.run "SET enable_profiling=true"
782
-
783
- # Check current settings
784
- db.fetch("SELECT * FROM duckdb_settings()").all
785
- ```
786
-
787
- ## Error Handling
788
-
789
- ### Exception Hierarchy
790
-
791
- The adapter maps DuckDB errors to appropriate Sequel exception types:
792
-
793
- ```ruby
794
- begin
795
- db[:users].insert(name: nil) # NOT NULL violation
796
- rescue Sequel::NotNullConstraintViolation => e
797
- puts "Cannot insert null name: #{e.message}"
798
- rescue Sequel::UniqueConstraintViolation => e
799
- puts "Duplicate value: #{e.message}"
800
- rescue Sequel::ForeignKeyConstraintViolation => e
801
- puts "Foreign key violation: #{e.message}"
802
- rescue Sequel::CheckConstraintViolation => e
803
- puts "Check constraint failed: #{e.message}"
804
- rescue Sequel::ConstraintViolation => e
805
- puts "Constraint violation: #{e.message}"
806
- rescue Sequel::DatabaseConnectionError => e
807
- puts "Connection error: #{e.message}"
808
- rescue Sequel::DatabaseError => e
809
- puts "Database error: #{e.message}"
810
- end
811
- ```
812
-
813
- ### Error Types
814
-
815
- | Sequel Exception | DuckDB Error Patterns | Description |
816
- | ------------------------------- | ----------------------------------------------------- | ------------------------------ |
817
- | `NotNullConstraintViolation` | `violates not null`, `null value not allowed` | NOT NULL constraint violations |
818
- | `UniqueConstraintViolation` | `unique constraint`, `duplicate key` | UNIQUE constraint violations |
819
- | `ForeignKeyConstraintViolation` | `foreign key constraint`, `violates foreign key` | Foreign key violations |
820
- | `CheckConstraintViolation` | `check constraint`, `violates check` | CHECK constraint violations |
821
- | `ConstraintViolation` | `constraint violation` | Generic constraint violations |
822
- | `DatabaseConnectionError` | `connection`, `cannot open`, `database not found` | Connection-related errors |
823
- | `DatabaseError` | `syntax error`, `parse error`, `table does not exist` | General database errors |
824
-
825
- ## Data Type Mappings
826
-
827
- ### Ruby to DuckDB Type Mapping
828
-
829
- | Ruby Type | DuckDB Type | Notes |
830
- | ---------------------- | ----------- | ---------------------------------- |
831
- | `String` | `VARCHAR` | Default string type |
832
- | `String` (large) | `TEXT` | For long text content |
833
- | `Integer` | `INTEGER` | 32-bit signed integer |
834
- | `Integer` (large) | `BIGINT` | 64-bit signed integer |
835
- | `Float` | `DOUBLE` | Double precision floating point |
836
- | `BigDecimal` | `DECIMAL` | Exact numeric with precision/scale |
837
- | `TrueClass/FalseClass` | `BOOLEAN` | Native boolean type |
838
- | `Date` | `DATE` | Date without time |
839
- | `Time/DateTime` | `TIMESTAMP` | Date and time |
840
- | `Time` (time-only) | `TIME` | Time without date |
841
- | `String` (binary) | `BLOB` | Binary data |
842
- | `Array` | `ARRAY` | DuckDB array types |
843
- | `Hash` | `JSON` | JSON data type |
844
-
845
- ### DuckDB to Ruby Type Mapping
846
-
847
- | DuckDB Type | Ruby Type | Conversion Notes |
848
- | -------------------- | ---------------------- | ------------------------------- |
849
- | `INTEGER`, `INT4` | `Integer` | 32-bit integer |
850
- | `BIGINT`, `INT8` | `Integer` | 64-bit integer |
851
- | `SMALLINT`, `INT2` | `Integer` | 16-bit integer |
852
- | `TINYINT`, `INT1` | `Integer` | 8-bit integer |
853
- | `REAL`, `FLOAT4` | `Float` | Single precision |
854
- | `DOUBLE`, `FLOAT8` | `Float` | Double precision |
855
- | `DECIMAL`, `NUMERIC` | `BigDecimal` | Exact numeric |
856
- | `VARCHAR`, `TEXT` | `String` | Text data |
857
- | `BOOLEAN` | `TrueClass/FalseClass` | Boolean values |
858
- | `DATE` | `Date` | Date only |
859
- | `TIMESTAMP` | `Time` | Date and time |
860
- | `TIME` | `Time` | Time only |
861
- | `BLOB`, `BYTEA` | `String` | Binary data as string |
862
- | `JSON` | `String` | JSON as string (parse manually) |
863
- | `ARRAY` | `Array` | Native array support |
864
- | `UUID` | `String` | UUID as string |
865
-
866
- ### Custom Type Handling
867
-
868
- ```ruby
869
- # Register custom type conversion
870
- db.conversion_procs[DuckDB::Type::UUID] = proc { |value|
871
- UUID.parse(value) if value
872
- }
873
-
874
- # Handle JSON columns
875
- class Product < Sequel::Model
876
- def metadata
877
- JSON.parse(super) if super
878
- end
879
-
880
- def metadata=(value)
881
- super(value.to_json)
882
- end
883
- end
884
- ```
885
-
886
- ## Performance Tuning
887
-
888
- ### Query Optimization
889
-
890
- ```ruby
891
- # Use EXPLAIN to analyze queries
892
- puts db[:users].join(:orders, user_id: :id).explain
893
-
894
- # Create appropriate indexes
895
- db.add_index :users, :email
896
- db.add_index :orders, [:user_id, :status]
897
- db.add_index :products, [:category_id, :active]
898
-
899
- # Use partial indexes for filtered queries
900
- db.add_index :products, :price, where: { active: true }
901
- ```
902
-
903
- ### Memory Management
904
-
905
- ```ruby
906
- # Configure memory limits
907
- db.run "SET memory_limit='4GB'"
908
- db.run "SET max_memory='8GB'"
909
-
910
- # Use streaming for large result sets
911
- db[:large_table].paged_each(rows_per_fetch: 1000) do |row|
912
- process_row(row)
913
- end
914
- ```
915
-
916
- ### Bulk Operations
917
-
918
- ```ruby
919
- # Efficient bulk insert
920
- data = 10000.times.map { |i| {name: "User #{i}", email: "user#{i}@example.com"} }
921
- db[:users].multi_insert(data)
922
-
923
- # Batch processing
924
- data.each_slice(1000) do |batch|
925
- db.transaction do
926
- db[:users].multi_insert(batch)
927
- end
928
- end
929
- ```
930
-
931
- ### Connection Pooling
932
-
933
- ```ruby
934
- # Optimize connection pool
935
- db = Sequel.connect(
936
- 'duckdb:///database.duckdb',
937
- max_connections: 20,
938
- pool_timeout: 10,
939
- pool_sleep_time: 0.001
940
- )
941
- ```
942
-
943
- This comprehensive API documentation covers all major aspects of using the Sequel DuckDB adapter. For the most up-to-date information, refer to the inline YARD documentation in the source code.