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