Class: ActiveRecord::ConnectionAdapters::NusaDBAdapter

Inherits:
AbstractAdapter
  • Object
show all
Defined in:
lib/active_record/connection_adapters/nusadb_adapter.rb

Constant Summary collapse

ADAPTER_NAME =
"NusaDB"
LAZY_CONNECTION =

ActiveRecord 7.2 rebuilt how an adapter gets its connection, and 8.0 removed the old way:

<= 7.1  ConnectionHandling#nusadb_connection opens the connection and hands it to
      .new(connection, logger, config); the adapter is simply born connected.
>= 7.2  the adapter is resolved from a registry and built with .new(config). It starts
      with no connection at all: the base class calls the private #connect on first use
      and owns the result as @raw_connection, which every statement must reach through
      #with_raw_connection so the base can verify, materialize transactions and retry.

Both eras are supported, so this constant -- not the ActiveRecord version number -- is what the code below branches on. #with_raw_connection arrived in 7.1, one release before the lazy-connection contract it belongs to, so a 7.1 adapter is connected eagerly and still talks through it; that is harmless, and it keeps the branch to a single question.

AbstractAdapter.private_method_defined?(:with_raw_connection) ||
AbstractAdapter.method_defined?(:with_raw_connection)
COLUMN_TAKES_CAST_TYPE =

ActiveRecord 8.1 inserted cast_type as Column's SECOND positional argument -- exactly where default used to sit. The old call still runs, it just hands the default over as the cast type, and the column dies later with undefined method 'mutable?' for nil, nowhere near the cause. The parameter list is asked rather than the version number because this is not the 7-vs-8 line one would guess: 8.0 kept the old signature and only 8.1 changed it.

Column.instance_method(:initialize).parameters.any? do |_kind, name|
  name == :cast_type
end

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.new_client(config) ⇒ Object

7.2+ calls this from #connect. The <= 7.1 hook at the bottom of this file calls it too, so a connection is opened the same way whichever era we are running under.



58
59
60
61
62
63
64
65
66
67
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 58

def new_client(config)
  config = config.symbolize_keys
  NusaDB.connect(
    host: config[:host] || "127.0.0.1",
    port: (config[:port] || 5678).to_i,
    user: config[:username] || config[:user] || "nusa-root",
    database: config[:database] || "nusadb",
    password: config[:password]
  )
end

.quote_column_name(name) ⇒ Object



125
126
127
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 125

def quote_column_name(name)
  %("#{name.to_s.gsub('"', '""')}")
end

.quote_string(s) ⇒ Object

Only ' is escaped, and deliberately: the server does not treat \ as an escape character inside a string literal, so doubling backslashes (as the ActiveRecord default does) would store them doubled.



136
137
138
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 136

def quote_string(s)
  s.gsub("'", "''")
end

.quote_table_name(name) ⇒ Object



129
130
131
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 129

def quote_table_name(name)
  quote_column_name(name)
end

Instance Method Details

#active?Boolean

A connection this adapter has not opened yet is not active -- and saying otherwise is not a white lie on 7.2+, where #active? is what tells the base class whether it still needs to #connect. Claiming true while @raw_connection is nil sends it straight to a NoMethodError.

Returns:

  • (Boolean)


73
74
75
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 73

def active?
  !current_raw_connection.nil?
end

#begin_db_transactionObject



188
189
190
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 188

def begin_db_transaction
  execute("BEGIN")
end

#columns(table_name, _name = nil) ⇒ Object



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 215

def columns(table_name, _name = nil)
  rows = nusa_query("SHOW COLUMNS FROM #{quote_table_name(table_name)}").rows
  rows.map do |row|
    col_name = row[0]
    sql_type = (row[1] || "text").to_s.downcase
    nullable = row[2].nil? || %w[t true yes 1].include?(row[2].to_s.downcase)
    cast_type = ActiveRecord::Type.lookup(map_type(sql_type))
     = (sql_type, cast_type)

    # The column has no default either way -- what moves is where +default+ sits in the
    # argument list. See COLUMN_TAKES_CAST_TYPE.
    if COLUMN_TAKES_CAST_TYPE
      Column.new(col_name, cast_type, nil, , nullable)
    else
      Column.new(col_name, nil, , nullable)
    end
  end
end

#commit_db_transactionObject



192
193
194
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 192

def commit_db_transaction
  execute("COMMIT")
end

#data_source_exists?(name) ⇒ Boolean Also known as: table_exists?

Returns:

  • (Boolean)


206
207
208
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 206

def data_source_exists?(name)
  tables.include?(name.to_s)
end

#data_sourcesObject



211
212
213
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 211

def data_sources
  tables
end

#disconnect!Object



77
78
79
80
81
82
83
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 77

def disconnect!
  super
  current_raw_connection&.close
  @raw_connection = nil if LAZY_CONNECTION
rescue StandardError
  nil
end

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



183
184
185
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 183

def exec_delete(sql, name = nil, binds = [], **_kwargs)
  log(sql, name) { nusa_query(sql) }.affected
end

#exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, **_kwargs) ⇒ Object



179
180
181
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 179

def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, **_kwargs)
  internal_exec_query(sql, name, binds)
end

#exec_query(sql, name = "SQL", binds = [], prepare: false, **_kwargs) ⇒ Object



175
176
177
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 175

def exec_query(sql, name = "SQL", binds = [], prepare: false, **_kwargs)
  internal_exec_query(sql, name, binds)
end

#exec_rollback_db_transactionObject



196
197
198
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 196

def exec_rollback_db_transaction
  execute("ROLLBACK")
end

#execute(sql, name = nil, **_kwargs) ⇒ Object

Every keyword splat below absorbs arguments ActiveRecord grew later and passes to adapters that never asked for them -- allow_retry: on #execute, returning: on #exec_insert, async: on #internal_exec_query (all 7.2+). Without them the caller gets an ArgumentError.



161
162
163
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 161

def execute(sql, name = nil, **_kwargs)
  log(sql, name) { nusa_query(sql) }
end

#internal_exec_query(sql, name = "SQL", binds = [], prepare: false, **_kwargs) ⇒ Object

#exec_query stopped being the way in. From 7.2 the internal read path (select_all -> select) calls #internal_exec_query, whose base implementation raises NotImplementedError -- so an adapter that overrides only the public #exec_query looks complete, loads fine, and then dies on the first SELECT. This is the real entry point; #exec_query is kept for <= 7.1 (where the base has no #internal_exec_query at all) and for anyone calling it directly.



170
171
172
173
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 170

def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, **_kwargs)
  result = log(sql, name) { nusa_query(sql) }
  ActiveRecord::Result.new(result.columns, result.rows)
end

#native_database_typesObject



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 101

def native_database_types
  {
    primary_key: "INT",
    string: { name: "TEXT" },
    text: { name: "TEXT" },
    integer: { name: "INT" },
    bigint: { name: "INT" },
    float: { name: "FLOAT" },
    decimal: { name: "NUMERIC" },
    boolean: { name: "BOOL" },
    date: { name: "DATE" },
    datetime: { name: "TIMESTAMP" },
    time: { name: "TIME" }
  }
end

#prepared_statementsObject



85
86
87
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 85

def prepared_statements
  false
end

#primary_keys(table_name) ⇒ Object

The primary-key columns of table_name, in key order, so ActiveRecord can auto-detect a model's primary key (single- or composite-column) without an explicit self.primary_key. Resolved from information_schema in two steps — the server does not support joining two information_schema tables in one query, so we look up the PRIMARY KEY constraint name and then its columns separately.



239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 239

def primary_keys(table_name)
  name = quote(table_name.to_s)
  constraint = nusa_query(
    "SELECT constraint_name FROM information_schema.table_constraints " \
    "WHERE constraint_type = 'PRIMARY KEY' AND table_name = #{name}"
  ).rows.first
  return [] if constraint.nil?

  nusa_query(
    "SELECT column_name FROM information_schema.key_column_usage " \
    "WHERE table_name = #{name} AND constraint_name = #{quote(constraint[0])} " \
    "ORDER BY ordinal_position"
  ).rows.map { |row| row[0] }
end

#quote_column_name(name) ⇒ Object

<= 7.1 has no class-level quoting to delegate to -- its Quoting module only ever calls the instance methods, whose default raises. Delegating explicitly keeps one implementation reachable from either era.



144
145
146
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 144

def quote_column_name(name)
  self.class.quote_column_name(name)
end

#quote_string(s) ⇒ Object



152
153
154
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 152

def quote_string(s)
  self.class.quote_string(s)
end

#quote_table_name(name) ⇒ Object



148
149
150
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 148

def quote_table_name(name)
  self.class.quote_table_name(name)
end

#supports_migrations?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 89

def supports_migrations?
  true
end

#supports_savepoints?Boolean

NusaDB supports savepoints (SAVEPOINT / ROLLBACK TO SAVEPOINT / RELEASE SAVEPOINT), so ActiveRecord can map nested transaction(requires_new: true) blocks onto them. The base adapter's create_savepoint / exec_rollback_to_savepoint / release_savepoint emit exactly the statements the server accepts, so enabling the capability is all that is needed.

Returns:

  • (Boolean)


97
98
99
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 97

def supports_savepoints?
  true
end

#tablesObject

--- schema introspection ---



202
203
204
# File 'lib/active_record/connection_adapters/nusadb_adapter.rb', line 202

def tables
  nusa_query("SHOW TABLES").rows.map { |row| row[0] }
end