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,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sequel"
4
+ require_relative "shared/duckdb"
5
+
6
+ # Sequel is a database toolkit for Ruby that provides a powerful ORM and database abstraction layer.
7
+ # This module extends Sequel to support DuckDB, a high-performance analytical database engine.
8
+ #
9
+ # @example Basic connection
10
+ # db = Sequel.connect('duckdb::memory:')
11
+ # db = Sequel.connect('duckdb:///path/to/database.duckdb')
12
+ #
13
+ # @example Connection with options
14
+ # db = Sequel.connect(
15
+ # adapter: 'duckdb',
16
+ # database: '/path/to/database.duckdb',
17
+ # config: { memory_limit: '2GB', threads: 4 }
18
+ # )
19
+ #
20
+ # @see https://sequel.jeremyevans.net/ Sequel Documentation
21
+ # @see https://duckdb.org/ DuckDB Documentation
22
+ module Sequel
23
+ # DuckDB adapter module for Sequel
24
+ #
25
+ # This module provides complete integration between Sequel and DuckDB, including:
26
+ # - Connection management for file-based and in-memory databases
27
+ # - SQL generation optimized for DuckDB's analytical capabilities
28
+ # - Schema introspection and metadata access
29
+ # - Data type mapping between Ruby and DuckDB types
30
+ # - Transaction support with proper error handling
31
+ # - Performance optimizations for analytical workloads
32
+ #
33
+ # @example Creating tables
34
+ # db.create_table :users do
35
+ # primary_key :id
36
+ # String :name, null: false
37
+ # String :email, unique: true
38
+ # Integer :age
39
+ # Boolean :active, default: true
40
+ # DateTime :created_at
41
+ # end
42
+ #
43
+ # @example Analytical queries
44
+ # sales_summary = db[:sales]
45
+ # .select(
46
+ # :product_category,
47
+ # Sequel.function(:sum, :amount).as(:total_sales),
48
+ # Sequel.function(:avg, :amount).as(:avg_sale)
49
+ # )
50
+ # .group(:product_category)
51
+ # .order(Sequel.desc(:total_sales))
52
+ #
53
+ # @since 0.1.0
54
+ module DuckDB
55
+ # Database class for DuckDB adapter
56
+ #
57
+ # This class extends Sequel::Database to provide DuckDB-specific functionality.
58
+ # It handles connection management, schema operations, and SQL execution for
59
+ # DuckDB databases. The class includes DatabaseMethods from the shared module
60
+ # to provide the core database functionality.
61
+ #
62
+ # @example Connecting to different database types
63
+ # # In-memory database (data lost when connection closes)
64
+ # db = Sequel::DuckDB::Database.new(database: ':memory:')
65
+ #
66
+ # # File-based database (persistent storage)
67
+ # db = Sequel::DuckDB::Database.new(database: '/path/to/database.duckdb')
68
+ #
69
+ # # With configuration options
70
+ # db = Sequel::DuckDB::Database.new(
71
+ # database: '/path/to/database.duckdb',
72
+ # config: { memory_limit: '4GB', threads: 8 }
73
+ # )
74
+ #
75
+ # @example Schema operations
76
+ # # List all tables
77
+ # db.tables # => [:users, :products, :orders]
78
+ #
79
+ # # Get table schema
80
+ # db.schema(:users) # => [[:id, {...}], [:name, {...}], ...]
81
+ #
82
+ # # Check if table exists
83
+ # db.table_exists?(:users) # => true
84
+ #
85
+ # @see DatabaseMethods
86
+ # @since 0.1.0
87
+ class Database < Sequel::Database
88
+ include Sequel::DuckDB::DatabaseMethods
89
+
90
+ # Set the adapter scheme for DuckDB
91
+ # This allows Sequel.connect('duckdb://...') to work
92
+ set_adapter_scheme :duckdb
93
+
94
+ # Connect to a DuckDB database
95
+ #
96
+ # Creates a connection to either a file-based or in-memory DuckDB database.
97
+ # This method handles the low-level connection establishment and error handling.
98
+ #
99
+ # @param server [Hash] Server configuration options from Sequel
100
+ # @option server [String] :database Database path or ':memory:' for in-memory database
101
+ # @option server [Hash] :config DuckDB-specific configuration options
102
+ # @option server [Boolean] :readonly Whether to open database in read-only mode
103
+ #
104
+ # @return [::DuckDB::Connection] Active DuckDB database connection
105
+ #
106
+ # @raise [Sequel::DatabaseConnectionError] If connection fails due to:
107
+ # - Invalid database path
108
+ # - Insufficient permissions
109
+ # - DuckDB library errors
110
+ # - Configuration errors
111
+ #
112
+ # @example Connect to in-memory database
113
+ # conn = connect(database: ':memory:')
114
+ #
115
+ # @example Connect to file database
116
+ # conn = connect(database: '/path/to/database.duckdb')
117
+ #
118
+ # @example Connect with configuration
119
+ # conn = connect(
120
+ # database: '/path/to/database.duckdb',
121
+ # config: { memory_limit: '2GB', threads: 4 }
122
+ # )
123
+ #
124
+ # @see disconnect_connection
125
+ # @see valid_connection?
126
+ # @since 0.1.0
127
+ def connect(server) # rubocop:disable Metrics/MethodLength
128
+ opts = server_opts(server)
129
+ database_path = opts[:database]
130
+
131
+ begin
132
+ if database_path == ":memory:" || database_path.nil?
133
+ # Create in-memory database and return connection
134
+ db = ::DuckDB::Database.open(":memory:")
135
+ else
136
+ # Fix URI parsing issue - add leading slash if missing for absolute paths
137
+ database_path = "/#{database_path}" if database_path.match?(/^[a-zA-Z]/) && !database_path.start_with?(":")
138
+
139
+ # Create file-based database (will create file if it doesn't exist) and return connection
140
+ db = ::DuckDB::Database.open(database_path)
141
+ end
142
+ db.connect
143
+ rescue ::DuckDB::Error => e
144
+ raise Sequel::DatabaseConnectionError, "Failed to connect to DuckDB database: #{e.message}"
145
+ rescue StandardError => e
146
+ raise Sequel::DatabaseConnectionError, "Unexpected error connecting to DuckDB: #{e.message}"
147
+ end
148
+ end
149
+
150
+ # Disconnect from a DuckDB database connection
151
+ #
152
+ # @param conn [::DuckDB::Connection] The database connection to close
153
+ # @return [void]
154
+ def disconnect_connection(conn)
155
+ return unless conn
156
+
157
+ begin
158
+ conn.close
159
+ rescue ::DuckDB::Error
160
+ # Ignore errors during disconnect - connection may already be closed
161
+ end
162
+ end
163
+
164
+ # Check if a DuckDB connection is valid and open
165
+ #
166
+ # @param conn [::DuckDB::Connection] The database connection to check
167
+ # @return [Boolean] true if connection is valid and open, false otherwise
168
+ def valid_connection?(conn)
169
+ return false unless conn
170
+
171
+ begin
172
+ # Try a simple query to check if the connection is still valid
173
+ conn.query("SELECT 1")
174
+ true
175
+ rescue ::DuckDB::Error
176
+ false
177
+ end
178
+ end
179
+
180
+ # Return the default dataset class for this database
181
+ #
182
+ # This method is called by Sequel to determine which Dataset class
183
+ # to use when creating new datasets for this database connection.
184
+ #
185
+ # @return [Class] The Dataset class to use for this database (always DuckDB::Dataset)
186
+ # @see Dataset
187
+ def dataset_class_default
188
+ Dataset
189
+ end
190
+ end
191
+
192
+ # Dataset class for DuckDB adapter
193
+ #
194
+ # This class extends Sequel::Dataset to provide DuckDB-specific SQL generation
195
+ # and query execution functionality. It includes DatasetMethods from the shared
196
+ # module for SQL generation and other dataset operations.
197
+ #
198
+ # The Dataset class is responsible for:
199
+ # - Generating DuckDB-compatible SQL for all operations (SELECT, INSERT, UPDATE, DELETE)
200
+ # - Executing queries against DuckDB databases
201
+ # - Handling result set processing and type conversion
202
+ # - Supporting DuckDB-specific features like window functions and CTEs
203
+ #
204
+ # @example Basic queries
205
+ # users = db[:users]
206
+ # users.where(active: true).all
207
+ # users.where { age > 25 }.count
208
+ # users.order(:name).limit(10).each { |user| puts user[:name] }
209
+ #
210
+ # @example Analytical queries with DuckDB features
211
+ # # Window functions
212
+ # db[:sales].select(
213
+ # :product_id,
214
+ # :amount,
215
+ # Sequel.function(:rank).over(partition: :category, order: Sequel.desc(:amount)).as(:rank)
216
+ # )
217
+ #
218
+ # # Common Table Expressions (CTEs)
219
+ # db.with(:high_spenders,
220
+ # db[:orders].group(:user_id).having { sum(:total) > 1000 }.select(:user_id)
221
+ # ).from(:high_spenders).join(:users, id: :user_id)
222
+ #
223
+ # @example Data modification
224
+ # # Insert single record
225
+ # users.insert(name: 'John', email: 'john@example.com', age: 30)
226
+ #
227
+ # # Bulk insert
228
+ # users.multi_insert([
229
+ # {name: 'Alice', email: 'alice@example.com'},
230
+ # {name: 'Bob', email: 'bob@example.com'}
231
+ # ])
232
+ #
233
+ # # Update records
234
+ # users.where(active: false).update(active: true, updated_at: Time.now)
235
+ #
236
+ # # Delete records
237
+ # users.where { created_at < Date.today - 365 }.delete
238
+ #
239
+ # @see DatasetMethods
240
+ # @since 0.1.0
241
+ class Dataset < Sequel::Dataset
242
+ include Sequel::DuckDB::DatasetMethods
243
+ end
244
+ end
245
+ end
246
+
247
+ # Register the DuckDB adapter with Sequel
248
+ # This registration allows Sequel.connect("duckdb://...") to automatically
249
+ # use the DuckDB adapter and create DuckDB::Database instances.
250
+ #
251
+ # @example Connection string usage
252
+ # db = Sequel.connect('duckdb::memory:')
253
+ # db = Sequel.connect('duckdb:///path/to/database.duckdb')
254
+ #
255
+ # @see Sequel::DuckDB::Database
256
+ Sequel::Database.set_shared_adapter_scheme :duckdb, Sequel::DuckDB