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.
- checksums.yaml +7 -0
- data/.kiro/specs/advanced-sql-features-implementation/design.md +24 -0
- data/.kiro/specs/advanced-sql-features-implementation/requirements.md +43 -0
- data/.kiro/specs/advanced-sql-features-implementation/tasks.md +24 -0
- data/.kiro/specs/duckdb-sql-syntax-compatibility/design.md +258 -0
- data/.kiro/specs/duckdb-sql-syntax-compatibility/requirements.md +84 -0
- data/.kiro/specs/duckdb-sql-syntax-compatibility/tasks.md +94 -0
- data/.kiro/specs/edge-cases-and-validation-fixes/requirements.md +32 -0
- data/.kiro/specs/integration-test-database-setup/design.md +0 -0
- data/.kiro/specs/integration-test-database-setup/requirements.md +117 -0
- data/.kiro/specs/sequel-duckdb-adapter/design.md +542 -0
- data/.kiro/specs/sequel-duckdb-adapter/requirements.md +202 -0
- data/.kiro/specs/sequel-duckdb-adapter/tasks.md +247 -0
- data/.kiro/specs/sql-expression-handling-fix/design.md +298 -0
- data/.kiro/specs/sql-expression-handling-fix/requirements.md +86 -0
- data/.kiro/specs/sql-expression-handling-fix/tasks.md +22 -0
- data/.kiro/specs/test-infrastructure-improvements/requirements.md +106 -0
- data/.kiro/steering/product.md +22 -0
- data/.kiro/steering/structure.md +88 -0
- data/.kiro/steering/tech.md +124 -0
- data/.kiro/steering/testing.md +192 -0
- data/.rubocop.yml +103 -0
- data/.yardopts +8 -0
- data/API_DOCUMENTATION.md +919 -0
- data/CHANGELOG.md +131 -0
- data/LICENSE +21 -0
- data/MIGRATION_EXAMPLES.md +740 -0
- data/PERFORMANCE_OPTIMIZATIONS.md +723 -0
- data/README.md +692 -0
- data/Rakefile +27 -0
- data/TASK_10.2_IMPLEMENTATION_SUMMARY.md +164 -0
- data/docs/DUCKDB_SQL_PATTERNS.md +410 -0
- data/docs/TASK_12_VERIFICATION_SUMMARY.md +122 -0
- data/lib/sequel/adapters/duckdb.rb +256 -0
- data/lib/sequel/adapters/shared/duckdb.rb +2349 -0
- data/lib/sequel/duckdb/version.rb +16 -0
- data/lib/sequel/duckdb.rb +43 -0
- data/sig/sequel/duckdb.rbs +6 -0
- 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
|