class CQL::Schema

Overview

The Schema class represents a database schema.

This class provides methods to build and manage a database schema, including creating tables, executing SQL statements, and generating queries.

Example Creating a new schema

AcmeDB = CQL::Schema.define(:acme_db, adapter: CQL::Adapter::SQLite, uri: "sqlite3://./db/acme.db") do
  table :users do
    primary :id, Int64, auto_increment: true
    column :name, String
    column :email, String
  end
end

Example Executing a SQL statement

schema.exec("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT)")

Example Creating a new query

query = schema.query

Included Modules

Defined in:

schema.cr

Constant Summary

Log = CQL.config.logger

Constructors

Class Method Summary

Macro Summary

Instance Method Summary

Instance methods inherited from module CQL::Executor

adapter : Adapter adapter, exec_query(& : DB::Connection -> _) exec_query

Constructor Detail

def self.new(name : Symbol, uri : String, adapter : Adapter, version : String = "1.0", quote_identifiers : Bool = false, source : ConnectionSource | Nil = nil) #

Initializes a new schema.

  • @param name [Symbol] the name of the schema
  • @param uri [String] the URI of the database
  • @param adapter [Adapter] the database adapter
  • @param version [String] the version of the schema (default: "1.0")

Example Initializing a new schema

schema = CQL::Schema.new(:acme_db, "sqlite3://./db/acme.db", CQL::Adapter::SQLite)

Example Running on the native PostgreSQL engine's pool

engine = CQL::Postgres::AsyncEngine.connect("postgres://app@db/app?pool_size=10")
schema = CQL::Schema.new(:app, "postgres://app@db/app", CQL::Adapter::Postgres,
  source: CQL::Postgres::EngineSource.new(engine))

[View source]

Class Method Detail

def self.define(name : Symbol, uri : String, adapter : Adapter, version : String = "1.0", quote_identifiers : Bool = false, source : ConnectionSource | Nil = nil, &) #

Builds a new schema.

  • @param name [Symbol] the name of the schema
  • @param uri [String] the URI of the database
  • @param adapter [Adapter] the database adapter
  • @param version [String] the version of the schema (default: "1.0")
  • @param quote_identifiers [Bool] whether generated SQL quotes identifiers (default: false)
  • @param source [ConnectionSource] where connections come from (default: crystal-db's pool opened from the URI)
  • @yield the block runs with the schema as its receiver, so #table declares a table
  • @return [Schema] the built schema

Example

AcmeDB = CQL::Schema.define(:acme_db, adapter: CQL::Adapter::SQLite, uri: "sqlite3://./db/acme.db") do
  table :users do
    primary :id, Int64, auto_increment: true
    column :name, String
    column :email, String
  end
end

[View source]

Macro Detail

macro method_missing(call) #

[View source]

Instance Method Detail

def adapter : Adapter #

[View source]
def alter(table_name : Symbol, &) #

Alter a table in the schema.

  • @param table_name [Symbol] the name of the table
  • @yield the block runs with an AlterTable as its receiver Example
schema.alter :users do
  add_column :phone, String
end

Example

schema.alter :users do
  drop_column :age
end

Where DDL is transactional, a rollback of the surrounding transaction also restores the table's declaration. The restore puts back the whole declaration as it was before this #alter, so it also undoes another fiber's uncommitted #alter of the same table.


[View source]
def build #

Builds the schema. This method creates the tables in the schema.

Example

schema.build

[View source]
def close : Nil #

Closes the connection source (the pool); later statements fail.


[View source]
def ddl_transaction(&) #

Runs schema changes atomically where DDL is transactional (a SAVEPOINT inside an open transaction); where DDL commits implicitly, runs them bare and refuses inside an open transaction.


[View source]
def delete #

Creates a new delete query for the schema.

  • @return [Delete] the new delete query Example
delete = schema.delete

[View source]
def dialect #

[View source]
def exec(sql : String) #

Executes a SQL statement.

  • @param sql [String] the SQL statement to execute

Example

schema.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")

[View source]
def exec_query(& : DB::Connection -> _) #

[View source]
def gen : Expression::Generator #

Returns a generator for SQL expression building. DEPRECATED: Use #new_generator instead for thread-safe SQL generation. This method creates a new generator each time to prevent concurrent access issues.

DEPRECATED Use new_generator for thread-safe SQL generation


[View source]
def insert #

Creates a new insert query for the schema.

  • @return [Insert] the new insert query Example
insert = schema.insert

[View source]
def migrator(config : MigratorConfig) #

Creates a new migrator with custom configuration for schema synchronization.

  • @param config [MigratorConfig] the configuration for the migrator
  • @return [Migrator] the new migrator with custom config Example
config = CQL::MigratorConfig.new(
  schema_file_path: "src/schemas/my_app_schema.cr",
  schema_name: :MyAppSchema,
  schema_symbol: :my_app_schema
)
migrator = schema.migrator(config)

[View source]
def migrator #

Creates a new migrator for the schema.

  • @return [Migrator] the new migrator Example
migrator = schema.migrator

[View source]
def name : Symbol #
  • @return [Symbol] the name of the schema

[View source]
def new_generator : Expression::Generator #

Creates a new expression generator for thread-safe SQL generation. Each call returns a fresh Generator instance to avoid state corruption when multiple fibers generate SQL concurrently.

  • @return [Expression::Generator] a new generator instance

Example

gen = schema.new_generator
sql, params = schema.query.from(:users).to_sql(gen)

[View source]
def pool_stats : PoolStats #

Connection pool occupancy, read without checking out a connection. All zeros before the first query when the pool was opened with initial_pool_size=0.


[View source]
def query #

Creates a new query for the schema.

  • @return [Query] the new query

Example

query = schema.query

[View source]
def quote_identifiers : Bool #
  • @return [Bool] whether generated SQL quotes table, column and alias names (opt-in; needed for reserved words such as order or group). Applies to queries and DML (SELECT/INSERT/UPDATE/DELETE); DDL emitted by #build, migrations and #alter is not quoted yet, so create such tables with raw SQL or a quoted migration.

[View source]
def schema_dumper #

Creates a schema dumper for this schema.

  • @return [SchemaDump] the new schema dumper Example
dumper = schema.schema_dumper

[View source]
def source : ConnectionSource #
  • @return [ConnectionSource] where connections and transactions come from: crystal-db's pool by default, or any other source (e.g. Postgres::EngineSource)

[View source]
def table(name : Symbol, as as_name : Symbol | Nil = nil, &) #

Declares a table in the schema; Table#create! or Schema#build creates it in the database.

  • @param name [Symbol] the name of the table
  • @param as [Symbol, String, nil] the alias of the table
  • @yield the block runs with the table as its receiver
  • @return [Table] the declared table Example
schema.table :tags do
  primary :id, Int64, auto_increment: true
  column :name, String
end

[View source]
def table(name : Symbol, as as_name : String | Nil = nil, &) #

[View source]
def tables : Hash(Symbol, Table) #
  • @return [Hash(Symbol, Table)] the tables in the schema

[View source]
def transaction(tx : DB::Transaction, &) #

[View source]
def transaction(&) #

Executes a block within a database transaction.

  • @yield [DB::Transaction] the transaction object
  • @return [Nil] the result of the block Example
schema.transaction do |tx|
  cnn = tx.connection
  cnn.exec("INSERT INTO users (name) VALUES (?)", "John")
  cnn.exec("UPDATE accounts SET balance = balance - 100 WHERE user_id = ?", 1)
end

Called inside another transaction on the same fiber, it runs as a SAVEPOINT on that transaction's connection.

Publishes Telemetry::TransactionEvents to Telemetry.subscribe_transactions subscribers. A manual tx.rollback followed by a normal return still reports Commit, since CQL cannot see it.

When the transaction rolls back (the block raises, including DB::Rollback, or the commit fails), table declarations changed by #alter inside it are restored. A savepoint that commits leaves them to the enclosing transaction. A manual tx.rollback restores nothing, for the same reason as above.


[View source]
def update #

Creates a new update query for the schema.

  • @return [Update] the new update query Example
update = schema.update

[View source]
def uri : String #
  • @return [String] the URI of the database: the string given, or for a schema with a source: the crystal-db-safe form of it (ConnectionSource#database_uri, e.g. without the engine's pool options)

[View source]
def version : String #
  • @return [String] the version of the schema

[View source]
def with_connection(&) #

Yields the fiber's transaction connection, or else one pooled connection, to pin statements to it with using(connection) (e.g. inside a transaction started on that connection).


[View source]