Module: ActiveRecord::ConnectionAdapters::Duckdb::DatabaseStatements

Included in:
ActiveRecord::ConnectionAdapters::DuckdbAdapter
Defined in:
lib/active_record/connection_adapters/duckdb/database_statements.rb

Instance Method Summary collapse

Instance Method Details

#build_result(result) ⇒ ActiveRecord::Result

Builds an ActiveRecord::Result from a DuckDB result

Parameters:

  • result (DuckDB::Result, nil)

    The DuckDB result to convert

Returns:

  • (ActiveRecord::Result)

    The converted ActiveRecord result



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 182

def build_result(result)
  # Handle DuckDB result format
  return ActiveRecord::Result.empty if result.nil?

  # DuckDB results have .columns and .to_a, not .rows
  columns = result.columns.map do |col|
    if col.respond_to?(:name)
      col.name
    elsif col.respond_to?(:column_name)
      col.column_name
    else
      col.to_s
    end
  end

  rows = result.to_a
  ActiveRecord::Result.new(columns, rows)
end

#cast_result(result) ⇒ ActiveRecord::Result

Casts a DuckDB result to ActiveRecord::Result format

Parameters:

  • result (DuckDB::Result, nil)

    The DuckDB result to cast

Returns:

  • (ActiveRecord::Result)

    The ActiveRecord-compatible result



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 64

def cast_result(result)
  return ActiveRecord::Result.empty(affected_rows: @affected_rows_before_warnings) if result.nil?

  columns = result.columns.map do |col|
    if col.respond_to?(:name)
      col.name
    elsif col.respond_to?(:column_name)
      col.column_name
    else
      col.to_s
    end
  end

  ActiveRecord::Result.new(columns, result.to_a)
end

#columns_for_insert(table_name) ⇒ Array<ActiveRecord::ConnectionAdapters::Column>

Returns columns that should be included in INSERT statements

Parameters:

  • table_name (String)

    The name of the table

Returns:

  • (Array<ActiveRecord::ConnectionAdapters::Column>)

    Columns to include in INSERT



151
152
153
154
155
156
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 151

def columns_for_insert(table_name)
  columns(table_name).reject do |column|
    # Exclude columns that have a default function (like nextval)
    column.default_function.present?
  end
end

#exec_delete(sql, name = nil, binds = []) ⇒ Integer Also known as: exec_update

Executes a DELETE statement and returns the number of affected rows

Parameters:

  • sql (String)

    The DELETE SQL statement

  • name (String, nil) (defaults to: nil)

    Optional name for logging

  • binds (Array) (defaults to: [])

    Array of bind parameters

Returns:

  • (Integer)

    Number of rows affected by the delete



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 108

def exec_delete(sql, name = nil, binds = []) # :nodoc:
  reconnect unless raw_connection

  if binds.any?
    # For bound queries, handle them with proper logging
    log(sql, name, binds) do
      bind_values = binds.map(&:value_for_database)
      raw_connection.query(sql, *bind_values)
    end.rows_changed
  else
    # For non-bound queries, use execute directly
    result = execute(sql, name)
    result.rows_changed
  end
end

#exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) ⇒ ActiveRecord::Result

Executes an INSERT statement with optional RETURNING clause

Parameters:

  • sql (String)

    The INSERT SQL statement

  • name (String, nil) (defaults to: nil)

    Optional name for logging

  • binds (Array) (defaults to: [])

    Array of bind parameters

  • pk (String, nil) (defaults to: nil)

    Primary key column name

  • sequence_name (String, nil) (defaults to: nil)

    Sequence name (unused)

  • returning (Array, nil) (defaults to: nil)

    Columns to return after insert

Returns:

  • (ActiveRecord::Result)

    The insert result with returned values



133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 133

def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
  # Rails 8 will pass returning: [pk] when it wants the ID back
  if returning&.any?
    returning_columns = returning.map { |c| quote_column_name(c) }.join(', ')
    sql = "#{sql} RETURNING #{returning_columns}" unless sql.include?('RETURNING')
  elsif pk && !sql.include?('RETURNING')
    # Add RETURNING for the primary key
    sql = "#{sql} RETURNING #{quote_column_name(pk)}"
  end

  # Execute the query and return the result
  # Rails 8 will handle extracting the ID from the result
  exec_query(sql, name, binds)
end

#exec_query(sql, name = nil, binds = [], prepare: false) ⇒ ActiveRecord::Result

Executes a query and returns an ActiveRecord::Result

Parameters:

  • sql (String)

    The SQL query to execute

  • name (String, nil) (defaults to: nil)

    Optional name for logging

  • binds (Array) (defaults to: [])

    Array of bind parameters

  • prepare (Boolean) (defaults to: false)

    Whether to prepare the statement (unused)

Returns:

  • (ActiveRecord::Result)

    The query result



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 86

def exec_query(sql, name = nil, binds = [], prepare: false)
  reconnect unless raw_connection

  log(sql, name, binds) do
    result = if binds.any?
               # Use the modern parameter binding approach
               exec_query_with_binds(sql, binds)
             else
               # Fallback to direct execution with quoted values
               # Your existing quote method will handle any unquoted values
               raw_connection.query(sql)
             end

    build_result(result)
  end
end

#execute(sql, name = nil) ⇒ DuckDB::Result

Executes a SQL statement against the DuckDB database

Parameters:

  • sql (String)

    The SQL statement to execute

  • name (String, nil) (defaults to: nil)

    Optional name for logging purposes

Returns:

  • (DuckDB::Result)

    The result of the query execution



19
20
21
22
23
24
25
26
27
28
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 19

def execute(sql, name = nil) # :nodoc:
  # In case a query is being executed before the connection is open, reconnect.
  reconnect unless raw_connection

  log(sql, name) do
    ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
      raw_connection.query(sql)
    end
  end
end

#fetch_type_metadata(sql_type) ⇒ ActiveRecord::ConnectionAdapters::SqlTypeMetadata

Fetches type metadata for a SQL type string

Parameters:

  • sql_type (String)

    The SQL type to get metadata for

Returns:

  • (ActiveRecord::ConnectionAdapters::SqlTypeMetadata)

    The type metadata



204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 204

def (sql_type)
  # Parse DuckDB types and map to Rails types
  type, limit, precision, scale = parse_type_info(sql_type)

  ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(
    sql_type: sql_type,
    type: type.to_sym,
    limit: limit,
    precision: precision,
    scale: scale
  )
end

#last_inserted_id(result) ⇒ Object

Extracts the last inserted ID from an insert result

Parameters:

  • result (ActiveRecord::Result)

    The result from an insert operation

Returns:

  • (Object)

    The last inserted ID value



170
171
172
173
174
175
176
177
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 170

def last_inserted_id(result)
  # Handle ActiveRecord::Result from RETURNING clause
  if result.is_a?(ActiveRecord::Result) && result.rows.any?
    id_value = result.rows.first.first
    return id_value
  end
  super
end

#perform_query(raw_connection, sql, binds, type_casted_binds, prepare: false, notification_payload: nil, batch: false, async: false, **kwargs) ⇒ DuckDB::Result

Performs a query with optional bind parameters

Parameters:

  • raw_connection (DuckDB::Connection)

    The raw database connection

  • sql (String)

    The SQL query to execute

  • binds (Array)

    Array of bind parameters

  • type_casted_binds (Array)

    Type-casted bind parameters

  • prepare (Boolean) (defaults to: false)

    Whether to prepare the statement (unused)

  • notification_payload (Hash, nil) (defaults to: nil)

    Payload for notifications (unused)

  • batch (Boolean) (defaults to: false)

    Whether this is a batch operation (unused)

  • async (Boolean) (defaults to: false)

    Whether to execute asynchronously (unused)

  • kwargs (Hash)

    Additional keyword arguments

Returns:

  • (DuckDB::Result)

    The query result



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 41

def perform_query(raw_connection,
                  sql,
                  binds,
                  type_casted_binds,
                  prepare: false,
                  notification_payload: nil,
                  batch: false,
                  async: false,
                  **kwargs)
  result = if binds.any?
             # Use the modern parameter binding approach
             exec_query_with_binds(sql, binds)
           else
             # Fallback to direct execution with quoted values
             # Your existing quote method will handle any unquoted values
             raw_connection.query(sql)
           end
  result
end

#return_value_after_insert?(column) ⇒ Boolean

Determines if a column value should be returned after insert This is crucial - it tells Rails which columns should use RETURNING

Parameters:

  • column (ActiveRecord::ConnectionAdapters::Column)

    The column to check

Returns:

  • (Boolean)

    true if column value should be returned after insert



162
163
164
165
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 162

def return_value_after_insert?(column)
  # Return true for any column with a sequence default
  column.default_function&.include?('nextval') || super
end

#write_query?(_sql) ⇒ Boolean

Determines if a SQL query is a write operation

Parameters:

  • _sql (String)

    The SQL query to check (unused in DuckDB implementation)

Returns:

  • (Boolean)

    always returns false for DuckDB



11
12
13
# File 'lib/active_record/connection_adapters/duckdb/database_statements.rb', line 11

def write_query?(_sql)
  false
end