You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every error class CQL defines, what raises it, and its message.
Error Codes
CQL's own classes inherit from CQL::Error < Exception.
begin
user.save!
rescue ex : CQL::RecordInvalid# an invalid record (a Validations::ValidationError)
ex.message # => "name must be present,email must match the pattern /@/"
ex.error_fields # => [:name, :email]rescue ex : CQL::RecordNotSaved# a before_* callback returned falserescue ex : CQL::Error# anything else CQL raisesend
Core errors (src/exceptions.cr)
Class
Raised by
Message and fields
CQL::Error
Raised directly for model and table misuse; see the next table
free text
CQL::RecordInvalid
An invalid record, always as its subclass CQL::ActiveRecord::Validations::ValidationError (see Active Record errors)
the error messages joined by ,; validation_errors, error_messages, error_fields
CQL::RecordNotSaved
save! when a callback halted the save
"Failed to save the record"
CQL::RecordNotFound
save, save! and update! of a saved record, Model.update!(record) and Model.update!(id, ...), when the UPDATE matched no row and a lookup by primary key finds none; not when DirtyTracking sends no UPDATE
"User: the UPDATE matched no row (it was deleted, or its insert rolled back)"; never the id
CQL::OptimisticLockError
update! and delete! on a model with optimistic_locking, and Update#with_optimistic_lock(...).commit, when no row matched the version
"Record has been modified by another process"; model_class, id, expected_version, actual_version (update! and delete! set model_class, id and expected_version; with_optimistic_lock(...).commit sets expected_version). The message names the model and versions, never the id
CQL::DialectError
SQL generation for a feature the adapter lacks; see Dialect errors
"SQLite does not support DISTINCT ON\n\nWorkaround:\n..."; dialect, feature, workaround
CQL::SQLiteUnsupportedFeatureError < DialectError
SQLite: back(...) on insert, update or delete; change_column; foreign_key and drop_foreign_key in schema.alter; add_column with a "CURRENT_TIMESTAMP" default on a Time column
"SQLite does not support ALTER COLUMN syntax"
CQL::MySqlUnsupportedFeatureError < DialectError
MySQL: a table with check_constraint
"MySQL does not support CHECK constraints (...)"
CQL::Error messages raised directly:
Message
Raised by
Example
Unknown column: <name>
Model.update!(id, attrs) / update!(**attrs) with a column the table lacks; touch on a model without updated_at
User.update!(1_i64, {:nope => "x".as(DB::Any)})
No attributes to update
Model.update!(id, {} of Symbol => DB::Any)
Column <name> cannot be null
Model.update!(id, attrs) setting nil on a NOT NULL column
Invalid type for column <name>: expected <T>, got <U>
Model.update!(id, attrs) with a wrongly typed value
Cannot touch on a new record object
touch on an unsaved record
User.new.touch
Primary key overflow: <id> exceeds Int32 range
create! on a Model(Int32)
Record must be persisted for optimistic locking, Version value missing for optimistic locking
update! on an optimistic_locking model
Specified version column '<c>' not found in table '<t>', No version column found in table '<t>' ...
to_sql and commit of an Update with with_optimistic_lock
Table name cannot be empty / cannot contain spaces / cannot start with a number
schema.table, CQL::Table.new
CQL::Table.new(:"1users", AppDB)
<tool> failed: <error output>, <tool> could not be run: <cause>
dialect.structure_dump on PostgreSQL and MySQL when pg_dump / mysqldump fails or is not installed
"pg_dump failed: pg_dump: error: connection to server ... failed"
mysqldump would read the database name "<name>" as an option
dialect.structure_dump on MySQL when the URI's database name starts with -
mysqldump would read the database name "--all-databases" as an option
Dialect errors
Feature
Raises on
Error
lock(...) (row locking)
SQLite
DialectError
explain(analyze: true)
SQLite
DialectError
distinct_on
SQLite, MySQL
DialectError
json_contains
SQLite
DialectError
within_distance
SQLite
DialectError
full(...) join
MySQL
DialectError
Insert#get / get!
SQLite, MySQL
DialectError (INSERT ... RETURNING)
dialect.structure_dump
CockroachDB
DialectError
schema.alter, schema.build, a table's create! / drop! / truncate!, Migrator.new inside an open transaction; up, up_to, down, down_to, rollback there only when they have a migration to run (with nothing to do they return)
MySQL, CockroachDB
DialectError ("MySQL does not support schema changes inside a transaction"), raised before any DDL runs
back(...) on insert, update, delete
SQLite
SQLiteUnsupportedFeatureError
change_column, foreign_key, drop_foreign_key in schema.alter; add_column with a "CURRENT_TIMESTAMP" default on a Time column
validate!, save! and the instance create! on an invalid record; save returns false instead. Class-level Model.create!(**attrs) does not validate.
the error messages joined by ,; error_fields, error_messages
CQL::ActiveRecord::SchemaMappingError
Model.schema when no CQL::Schema.define block, or more than one, declares the inferred table; with CQL_VALIDATE_SCHEMA_MAPPINGS=1, after the program's other top-level code has run, when the table is missing, a getter type differs from the column type, or the primary key column differs
CQL schema mapping error in User: schema column `users.age` type Int32 does not match model getter `age` type Int64. ...
Instance delete! / force_delete! with a has_many ..., dependent: :restrict_with_error that has records, or a dependent: :destroy child whose delete! returns false; a DB::Error inside an association operation (not a native CQL::Postgres::ConnectionLost, which is not a DB::Error)
"Cannot delete record because dependent posts exist", "... could not be deleted", "Database operation failed (DB::MappingException)", with the original error as cause. The cause keeps its message, which can quote row values, and logging the error with exception: or leaving it unhandled prints it (Caused by:): redact before shipping logs
...::AssociationNotFound < RelationError
first / last on an empty has_many collection; updating a missing has_one / belongs_to target
"No records found in collection", "No profile to update"
...::InvalidAssociation < RelationError
Reading a belongs_to ..., optional: false whose key is nil or 0; clearing a non-optional belongs_to
"Required association user foreign key is nil or 0"
An error other than ArgumentError raised while a scope's body runs (an ArgumentError passes through as it is)
"Error in scope 'recent' (DB::Error)", with the original error as cause; as with RelationError, the cause's message can quote row values
Schema and migration errors
Class
Raised by
Message
CQL::Schema::Error
schema.alter :name for a table not in the schema
"Table 'users' not found"
CQL::Schema::InvalidURIError < Schema::Error
Schema.define / Schema.new: unparseable URI, no path, a PostgreSQL or CockroachDB URI with an empty host and no ?host= or PGHOST, a MySQL URI with an empty host, an unknown scheme
"Invalid URI format", "Missing host in MySQL URI", "Unsupported database type: oracle"
CQL::Migrator::Error
update_schema_file and bootstrap_schema (also run by CQL.build_migrator, CQL.bootstrap_schema and CQL.verify_schema(auto_fix: true)) when the dump or the write fails; after up / down with auto_sync the same failure only logs a warning and keeps the old file; up_to / down_to with a version no migration has; Migrator.new when two migrations share a version, before it creates or records anything; up when another migrator recorded a migration's version during the run; up when, while it waited for the migration lock, another migrator rolled back a migration older than those it picked (nothing more runs); on PostgreSQL, Migrator.new or a run with migrations to run when another migrator holds the migration lock past MigratorConfig#lock_timeout (nothing more runs)
"Schema update failed: <cause>", "No migration with version 42", "Migrations A and B share version 7; renumber the one cql_schema_migrations does not list. If it already ran, first insert ...", "Another migrator holds the migration lock on cql_schema_migrations; gave up after 60.0 s (MigratorConfig#lock_timeout) and ran nothing. ...", "Another migrator rolled back AddUsers while this one waited for the migration lock; AddPosts and newer were not applied"
CQL::SchemaDump::Error
SchemaDump for an unknown adapter or a failed connection; generate_schema_content and dump_to_file (before writing anything) for a name a schema built from the dump would not reproduce, listed below. Through a migrator it reaches you wrapped in Migrator::Error
"Failed to connect to database: ...", "Cannot dump table ..."
KeyError
schema.tables[:name] for an unknown table
"Missing hash key: :name"
CQL::SchemaDump::Error refuses these names, as CQL's DDL writes names unquoted. <kind> is name (the table's), column, foreign key column, referenced table or referenced column, and the name is printed escaped (String#dump), so an invisible character shows.
Message ends with
Name refused
Example
is not a plain identifier; CQL's DDL does not quote names, so building a schema from it would run SQL in the name. Rename it first
Anything but letters of any script, digits and _ (or starting with a digit); not valid UTF-8; not in NFC; holding a Hangul filler (U+115F, U+1160, U+3164, U+FFA0)
Cannot dump table "notes": column "a\u3164" is not a plain identifier; ...
is a keyword CQL's unquoted DDL reads as SQL, so the schema would build differently. Rename it first
constraint and like, in any case
Cannot dump table "notes": column "constraint" is a keyword ...
is folded to "<folded>" by the database in CQL's unquoted DDL, so it would name another table or column. Rename it first
A name the dialect's fold_unquoted_name changes: an A-Z letter on PostgreSQL; any uppercase letter, ı or İ on CockroachDB
Cannot dump table "Users": name "Users" is folded to "users" ...
is not an ASCII identifier, which CQL's foreign key DDL requires, so the dumped schema would not build. Rename it first
A foreign key column, referenced table or referenced column outside CQL::Identifier::PATTERN
Cannot dump table "posts": foreign key column "caf\u00E9_id" is not an ASCII identifier, ...
is not one of cascade, restrict, set_null, set_default, no_action
An on_delete / on_update rule CQL does not model
Cannot dump table "posts": foreign key rule "weird" is not one of ...
A failing statement inside up or down raises the driver's own exception. On SQLite and PostgreSQL the whole up / down run rolls back; on MySQL and CockroachDB each statement commits on its own. There is no IrreversibleMigration class: raise any exception from down.
Native PostgreSQL driver errors
All inherit from CQL::Postgres::Error < CQL::Error. Messages never contain credentials or bound values.
Class
Raised when
ProtocolError
The bytes on the wire do not follow protocol 3.0
UnrecognizedStartupReply < ProtocolError
The first reply is not a protocol message (e.g. CockroachDB answering HTTP/2)
UnsupportedProtocol
The server speaks no protocol version the connection may use
AuthenticationError
The authentication exchange cannot complete
TLSRefused
sslmode requires TLS and the server refused it or the handshake / certificate check failed
ConnectionLost
The socket failed during or after a request, so the statement may have run; crystal-db's pool does not retry it
ConnectionClosed < ConnectionLost
The connection was already closed, so nothing was sent; through crystal-db it is the cause of a DB::ConnectionLost, which the pool retries
QueryTimeout
No answer within the read timeout; the statement is canceled on the server
PoolTimeout / PoolClosed
The AsyncEngine pool had no free connection in time / is closed
CircuitOpen
The native engine temporarily refuses new connections after repeated connect/setup failures; wait for connect_retry_delay and investigate the underlying outage
TransactionClosed
A TransactionScope was used after its transaction ended
WrongFiber
A RowStream or TransactionScope was used by another fiber; open a separate stream or transaction in that fiber
StreamClosed
A RowStream was read after its block ended or its statement failed
UnsupportedValue
A valid PostgreSQL value Crystal cannot represent (infinite or far-out dates)
ServerError
The server sent an ErrorResponse; fields severity, sqlstate, server_message, detail, hint, position, schema, table, column, constraint, routine; stale_plan?
ServerError.from picks a subclass by SQLSTATE, then by SQLSTATE class.
message keeps the server's wording only for value-free SQLSTATE classes (08 0A 23 25 28 3D 53 54 55 57 58); otherwise it is a fixed title such as "ERROR: syntax error (SQLSTATE 42601)". See the native PostgreSQL driver guide.
crystal-db and standard errors
Class
Raised by
Message
DB::NoResultsError
find!, find_by!, first! and last! (class and QueryBuilder); Repository#find!; Query#get!; Insert#get!; reading a belongs_to ..., optional: false whose target is gone
"No records found with id=999", "No records found with conditions: {email: "x"}", "No records found", "The query returned no row", "The insert returned no row"
DB::ConnectionRefused
Opening a connection fails (crystal-db's first connect; the native driver maps transport and TLS failures to it)
DB::PoolTimeout, DB::PoolRetryAttemptsExceeded
A saturated pool; a failed lazy reconnect
URI::Error
CQL.build_schema with a db URL URI.parse rejects, such as sqlite3://:memory:
"Invalid URI: bad port at character 11"
Driver errors (SQLite3::Exception, MySQL and pg errors, CQL::Postgres::ServerError)
A statement the database rejects
ArgumentError
There is no ConfigurationError, TableNotFound or ColumnNotFound class. These raise ArgumentError:
"Use skip_locked or nowait, not both", "Unknown lock mode :other (use :update or :share)"
do_update / do_nothing without on_conflict, or on_conflict without columns
"Call on_conflict(...) before do_update"
Insert#get without back
"Insert#get needs the columns to return; call `back` first"
foreign_key with mismatched column counts
"Number of columns (...) must match number of referenced columns (...)"
An index name longer than 63 characters
"Index name "..." is longer than 63 characters; give the index a shorter name"
find_each / find_in_batches / insert_all with batch_size <= 0
"batch_size must be positive, got 0"
preload after a partial select
"preload is not supported after a partial select"
preload with a name the model declares no association by
"User has no association :postz. Associations: profile, posts"
A scope called with the wrong arguments or returning a non-query
"Scope 'active' expects no arguments ..."; a scope body that raises another error gives a ScopeError (see Active Record errors)
contains, starts_with or ends_with with a NUL in the text, when the condition is built
"LIKE text must not contain NUL: SQLite would end the pattern there"
CQL::Schema.new(..., source:) with a source bound to another adapter
"the connection source speaks ..."
Native PostgreSQL driver: unsupported sslmode, libpq-only options, sslrootcert without verify-full, sslcert without sslkey or an unreadable one, non-numeric pool options
"the sslpassword option is not supported by this driver", "sslkey /certs/client.key is not a readable file"
Native PostgreSQL driver: a pool_reset other than discard_all; pool_reset on a URL opened through crystal-db (DB.open, a CQL::Schema without a source)
"pool_reset must be discard_all, got "reset_all"", "pool_reset needs the engine's pool, as crystal-db's cannot reset a returned connection: use CQL::Postgres::EngineSource or AsyncEngine"
Compile-time errors
The compiler stops with these messages; there is no runtime class.
Message starts with
Cause
CQL belongs_to error in Post:
Name or foreign key not a symbol literal; foreign key getter missing; key type differs from the target primary key type
CQL has_many error in User:
Name or foreign_key not a symbol literal; unsupported dependent (allowed: destroy delete_all nullify restrict_with_error); target lacks the key; key type mismatch
CQL has_many error ...: foreign key ... is the primary key of ...
Shared primary keys are not supported
CQL validation error in User:
Unsupported predicate; validate on an unknown attribute; missing confirmation target
CQL order_by: User has no column
order_by with an unknown column
CQL: cannot compare a String column with an integer
A typed where { } comparison with the wrong value type
CQL: Symbol cannot be bound as a SQL parameter
A raw fragment value that is not a DB::Any type, as in where("name = ?", :admin)
Defined, not raised by CQL
These CQL:: classes are defined, and some are rescued, but nothing in CQL raises them: RecordNotFoundError, ValidationError (not the Active Record one above), ConnectionError, QueryError, TransactionError, MigrationError, AttributeError, PersistenceError, CallbackError, Schema::VersionConflictError and Schema::ConnectionError.