PGAdapter has experimental support for ActiveRecord 7.x.
This document shows how to use this sample application, and lists the
limitations when working with ActiveRecord
with PGAdapter.
The application.rb file contains a sample application using ActiveRecord
with PGAdapter.
Use this as a reference for features of ActiveRecord
that are supported with PGAdapter. This sample
assumes that the reader is familiar with ActiveRecord
, and it is not intended as a tutorial for how
to use ActiveRecord
in general.
See Limitations for a full list of known limitations when working with ActiveRecord
.
The sample application automatically starts PGAdapter and the Spanner emulator in a Docker container. You must have Docker installed on your local system to run the sample.
bundle exec rake run
The database is automatically created on the emulator when the application is started. This is
handled by the gcr.io/cloud-spanner-pg-adapter/pgadapter-emulator
Docker image. See the
PGAdapter emulator documentation for more information about connecting
PGAdapter to the emulator.
You can also run the sample on a real Cloud Spanner PostgreSQL database instead of the emulator. The sample application will automatically start PGAdapter in a Docker container and connect to your Cloud Spanner database. For this, you need to follow these configuration steps
- Set the environment variable
GOOGLE_APPLICATION_CREDENTIALS
to point to a valid (service) account file that should be used to run the sample. - Modify the
config/database.yml
file so it points to your database.
Alternatively, you can also set the following environment variables to configure the database and port number:
GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
PGDATABASE=projects/my-project/instances/my-instance/databases/my-database
Make sure the database that you point the sample to is empty, as the sample application will automatically create the required data model in that database.
Run the sample on Cloud Spanner with the following command:
bundle exec rake run_prod
You need to add some extra options to your Ruby ActiveRecord configuration to work with Cloud Spanner.
The following code snippets show the additional options that must be set when working with PGAdapter and Cloud Spanner. These options ensure that:
- ActiveRecord uses
timestamptz
instead oftimestamp(6)
as the default date/time data type. advisory_locks
are not used for migrations.- DDL transactions are converted to DDL batches. See DDL options for more information.
pg_class
and related tables are emulated by PGAdapter.
The following initialization code makes sure that ActiveRecord will use timestamptz
as the default
date/time type for PostgreSQL databases. In addition, it will prevent ActiveRecord from using type
modifiers for timestamptz
data types (e.g. timestamptz(6)
).
This initialization code is only required for executing migrations. You may omit this from your initialization code when you are not running migrations.
This sample application has this initialization code in the config/environment.rb
file.
# Make sure that the PostgreSQL-adapter uses timestamptz without any type modifiers.
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
module ActiveRecord::ConnectionAdapters
class PostgreSQLAdapter
def supports_datetime_with_precision?
false
end
end
end
NOTE: The datetime_type
option was added in ActiveRecord 7.0.
Use the following configuration for ActiveRecord 6.1 or lower:
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:datetime] = { name: "timestamptz" }
module ActiveRecord::ConnectionAdapters
class PostgreSQLAdapter
def supports_datetime_with_precision?
false
end
end
end
The following is the database.yml
that is used by this sample. It uses the standard
PGHOST
, PGPORT
and PGDATABASE
environment variables. Note that the sample application
starts PGAdapter automatically on a random port, and the PGPORT
environment variable is
automatically updated to use this port.
default: &default
adapter: postgresql
host: <%= ENV['PGHOST'] || "localhost" %>
port: <%= ENV['PGPORT'] || "5432" %>
database: <%= ENV['PGDATABASE'] || "projects/my-project/instances/my-instance/databases/my-database" %>
pool: 5
# Advisory locks are not supported by PGAdapter
advisory_locks: false
# These settings ensure that migrations and schema inspections work.
variables:
"spanner.ddl_transaction_mode": "AutocommitExplicitTransaction"
"spanner.emulate_pg_class_tables": "true"
development:
<<: *default
The data model is automatically created using a standard ActiveRecord migration script. See db/migrate/01_create_tables.rb for the migration script.
You can drop the data model again using the drop_data_model.sql
script in this folder.
The following limitations are currently known:
Limitation | Workaround |
---|---|
Schema Dumper | Cloud Spanner does not support all PostgreSQL pg_catalog tables. Using ActiveRecord::SchemaDumper to get export the current schema is not guaranteed to include all objects in the database. |
DDL Transactions | Cloud Spanner does not support DDL statements in a transaction. Add "spanner.ddl_transaction_mode": "AutocommitExplicitTransaction" to the variables section in your database.yml file to automatically convert DDL transactions to non-atomic DDL batches. See config/database.yml for an example. |
Generated primary keys | The serial data type is not supported. Use bit-reversed sequences to auto-generate primary keys. |
Upsert | upsert and upsert_all are not supported. |
SELECT ... FOR UPDATE | Pessimistic locking / SELECT ... FOR UPDATE is not supported. |
Transaction isolation level | Only isolation level :serializable is supported. |
Dumping the schema of a database is not guaranteed to produce a complete result. There is currently no workaround for this limitation.
The serial
data type is currently not supported.
Generated primary keys can however be used in combination with bit-reversed sequences.
The TicketSale model in this sample uses an auto-generated primary key. The table definition for this model looks like this:
execute "create sequence ticket_sale_seq
bit_reversed_positive
skip range 1 1000
start counter with 50000;"
create_table :ticket_sales, id: false, primary_key: :id do |t|
t.column :id, "bigint not null primary key default nextval('ticket_sale_seq')"
t.references :concert, foreign_key: {primary_key: :concert_id},
type: :string, limit: 36, index: false
t.string :customer_name
t.numeric :price
t.string :seats, array: true
t.datetime :created_at
t.datetime :updated_at
t.integer :lock_version, null: false
end
See https://cloud.google.com/spanner/docs/schema-design#primary-key-prevent-hotspots for more information on choosing a good primary key. Most models in this sample use random UUIDs that are generated by the client and stored as strings for primary keys.
Example:
Singer.create({singer_id: SecureRandom.uuid,
first_name: 'Bob',
last_name: 'Anderson'})
INSERT ... ON CONFLICT ...
are not supported by Cloud Spanner and should not be used. Trying to
use upsert
or upsert_all
will fail.
Locking clauses, like SELECT ... FOR UPDATE
, are not supported (see also https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html).
These are normally also not required, as Cloud Spanner uses isolation level serializable
for
read/write transactions.