How do I tell my migration what column type to use? - postgresql

I'm using Rails 5 and PostGres 9.5. I have created this migration
class CreateSearchCodeTable < ActiveRecord::Migration[5.0]
def change
create_table :search_codes do |t|
t.string :code
t.references :address, index: true, foreign_key: true, on_delete: :cascade
t.index ["code"], name: "index_search_codes_on_code", unique: true, using: :btree
end
end
end
The ID column of the address table is not an integer though. Maybe for that reason, I get the below error when I run the migration
== 20171011202623 CreateSearchCodeTable: migrating ============================
-- create_table(:search_codes)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
PG::DatatypeMismatch: ERROR: foreign key constraint "fk_rails_6bd9792e3b" cannot be implemented
DETAIL: Key columns "address_id" and "id" are of incompatible types: integer and character varying.
: CREATE TABLE "search_codes" ("id" serial primary key, "code" character varying, "address_id" integer, CONSTRAINT "fk_rails_6bd9792e3b"
FOREIGN KEY ("address_id")
REFERENCES "addresses" ("id")
)
How do I instruct my migration to create my column with the same type as the referenced column?

t.references delegates job to add_reference and this one accepts :type parameter. Judging from this, you should be able to do
t.references :address, type: :string, index: true, foreign_key: true, on_delete: :cascade
^^^^^^^^^^^^^
Just tested this on a toy sqlite3-based project, it worked. Should "just work" on pg too.
How do I instruct my migration to create my column with the same type as the referenced column?
If you meant to tell it to infer the type of the other column and have type of this one match it, then this is likely not possible. But you can always specify type explicitly.

Related

Sequelize upsert throws unique violation for schema with nullable unique column

I'm facing an unique constraint violation issue when doing an upsert, because the UPDATE query built by sequelize ignores the partial index constraint defined by the model (unless it doesn't matter). I'm new to node+sequelize so I might be missing something obvious, but I went through all the potential places for finding the appropriate answers, inclusive of the sequelize code, but I'm not able to find the answer I'm looking for. Really appreciate your help!
My current versions:
"pg": "7.9.0",
"sequelize": "5.21.3"
I have a model that consists of a primary key: id and two other unique indexes of which one of them is a nullable field.
module.exports.Entities = sequelize.define('entities', {
id: {type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, allowNull: false, primaryKey: true},
cId: {type: Sequelize.STRING, allowNull: false},
pId: {type: Sequelize.UUID, allowNull: false},
eKey: {type: Sequelize.INTEGER, allowNull: true}
}, {
indexes: [
{
name: 'unique_c_id_p_id',
fields: ['c_id', 'p_id'],
unique: true
},
{
name: 'unique_e_key',
fields: ['e_key'],
unique: true,
where: {
eKey: {
[Op.not]: null
}
}
}
]
})
and the table itself looks like below:
CREATE TABLE public.entities (
id UUID DEFAULT uuid_generate_v4 (),
c_id UUID NOT NULL,
p_id UUID NOT NULL,
e_key INTEGER DEFAULT NULL,
CONSTRAINT ENTITY_SERVICE_PKEY PRIMARY KEY (id),
CONSTRAINT unique_c_id_p_id UNIQUE (c_id, p_id)
);
CREATE UNIQUE INDEX unique_e_key ON public.entities (e_key) WHERE e_key IS NOT NULL;
The upsert method call looks like:
module.exports.upsert = async (Model, values) => Model.upsert(values, {returning: true})
I pass the above Entities model, and the below value as arguments to this function.
{
"id"="3169d4e2-8e2d-451e-8be0-40c0b28e2aa9",
"c_id"="00000000-0000-0000-0000-000000000000",
"p_id"="78bce392-4a15-4a8a-986b-c9398787345f",
"e_key"= null
}
Issue: SequelizeUniqueConstraintError
Sequelize tries to do an insert followed by an update query when we attempt to update an existing record using the upsert method.
The insert query shows a conflict, since the record exists already, and sequelize upsert call proceeds on to invoke the update query.
However, the query that it builds to UPDATE looks something like below:
"SQL statement UPDATE entities SET id='3169d4e2-8e2d-451e-8be0-40c0b28e2aa9',c_id='00000000-0000-0000-0000-000000000000',p_id='78bce392-4a15-4a8a-986b-c9398787345f',e_key=NULL
WHERE (id = '3169d4e2-8e2d-451e-8be0-40c0b28e2aa9'
OR e_key IS NULL
OR (c_id = '00000000-0000-0000-0000-000000000000' AND p_id = '78bce392-4a15-4a8a-986b-c9398787345f'))
RETURNING id\nPL/pgSQL function pg_temp_5.sequelize_upsert() line 1 at SQL statement"
Now, I do understand the reason why it's throwing the unique constraint violation, since in the above query's WHERE clause sequelize calls OR e_key IS NULL since e_key = null and that could potentially return more than 1 record, and the SET is trying to update the same value for all those records that were returned thereby violating the primaryKey constraints, unique constraints etc.
What I would like to understand is that:
Why does sequelize not exclude the e_key unique constraint based on the partial index defined given that it picks the WHERE clause attributes based on the constraints defined in the Model & it's indexes?
Is there anything that I could do to get past this issue?
Or, am I missing something obvious that I could fix and try?
Really appreciate you taking your time to read and respond. Thanks!

Laravel 5.3 Eloquent transactions and foreign key restrictions

Am working on bigger project where we have multiple schemas in one Postgres DB. We have created foreign keys between schemas. Here is an example >
We have company schema and user schema. Company schema has company_users table which have foreign key restriction on user.users table
CREATE TABLE company.company_user
(
id serial NOT NULL,
company_id integer NOT NULL,
user_id integer NOT NULL,
created_at timestamp(0) without time zone,
updated_at timestamp(0) without time zone,
deleted_at timestamp(0) without time zone,
CONSTRAINT company_user_pkey PRIMARY KEY (id),
CONSTRAINT company_user_company_id_foreign FOREIGN KEY (company_id)
REFERENCES company.companies (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT company_user_user_id_foreign FOREIGN KEY (user_id)
REFERENCES "user".users (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Following queries run in Postgres without issue
BEGIN;
insert into "db"."user"."users" (id,"surname", "firstname", "email", "position", "language_id", "is_super_admin", "updated_at", "created_at") values (156,'Mueller', 'Julianne', 'julianne.mueller1#example.org', 'Nuclear Power Reactor Operator', 41, false, '2017-01-13 12:35:10', '2017-01-13 12:35:10') returning "id";
insert into "db"."company"."company_user" ("company_id", "user_id", "updated_at", "created_at") values (4445, 156, '2017-01-13 12:35:10', '2017-01-13 12:35:10') returning "id";
COMMIT;
However if i perform same queries via Eloquent in Laravel
\DB::beginTransaction();
$user = new User(["surname" => 'Mueller',
"firstname" => 'Julianne',
"email" => 'julianne.mueller1#example.org',
"position" => 'Nuclear Power Reactor Operator',
"language_id" => 41,
"is_super_admin" => false]
);
if (!$user->save()) {
\DB::rollBack();
return false;
}
\Log::error($user->id);
$company_user = new CompanyUser([
"company_id" => 4445,
"user_id" => $user->id
]);
if (!$company_user->save()) {
\DB::rollBack();
return false;
}
\DB::commit();
is throwing folloing error (it seems that it cannot find id of user in the table)
PDOException: SQLSTATE[23503]: Foreign key violation: 7 ERROR: insert or update on table "company_user" violates foreign key constraint "company_user_user_id_foreign"
Would anyone can say why this is not working? \Log::error($user->id) is printing id of inserted user. I tried to print out queries from Laravel with DB listener, all queries are executed in correct order, but am still getting this error.
Ok so we found a solution. It seems that we need to start transaction for each of schemas separately + each foreign key that are referencing different schema than their own should be created as deferred.
Make sure CompanyUser has $fillable:
$fillable = ['user_id', 'company_id'];
Also, make sure there is a user with this ID is already in users table. Maybe you'll need to get rid of transaction.

Sequelize Duplicate Key Constraint Violation

I am trying to add a many to many relationship through an explicitly created junction table using Sequelize and Postgresql.
The tables on either side of the relationship are associated like this:
Shop.belongsToMany(models.user, {through: 'visits' })
User.belongsToMany(models.shop, {through: 'visits' })
And the visits junction table primary key is defined like this:
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true // Automatically gets converted to SERIAL for postgres
}
When I try and insert into visits I get the following error:
ERROR: duplicate key value violates unique constraint "visits_shopId_userId_key"
DETAIL: Key ("shopId", "userId")=(1, 12) already exists.
After doing a pg_dump, I have tried to remove the composite key constraint by adding constraint: false to the models, but I still get the error.
(I have dropped the tables and resynced several times during the debugging process)
After digging around the Sequelize issues, it turns out that removing the constraint on the N:M composite key is an easy fix.
The through key can take an object with the unique: false property:
Shop.belongsToMany(models.user, {
through: {
model: 'visits',
unique: false
},
constraints: false
});

Modify Devise to support UUID primary key

I want to modify Devise to make it work with a users table with a UUID primary key with PostgreSQL.
Here is the migration:
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table :users, id: false do |t|
t.uuid :uuid, null: false
# ...
end
change_table :users do |t|
t.index :uuid, unique: true
# ...
end
end
def migrate(direction)
super
if direction == :up
# This is only necessary because the following does not work:
# t.uuid :uuid, primary: true, null: false
execute "ALTER TABLE users ADD PRIMARY KEY (uuid);"
end
end
end
Here is the User model:
class User < ActiveRecord::Base
primary_key = :uuid
devise :database_authenticatable, :recoverable, :registerable,
:rememberable, :trackable, :validatable
validates :uuid, presence: true
before_validation :ensure_uuid
def ensure_uuid; self.uuid ||= SecureRandom.uuid end
end
Here is the error:
PG::Error: ERROR: operator does not exist: uuid = integer
LINE 1: ...ECT "users".* FROM "users" WHERE "users"."uuid" = 1 ORDER...
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
: SELECT "users".* FROM "users" WHERE "users"."uuid" = 1 ORDER BY "users"."uuid" ASC LIMIT 1
Extracted source (around line #5):
1 .navbar-inner
2 .container
3 = a_nav_tag "App", root_path
4 - if user_signed_in?
5 %ul.nav.pull-right
6 %li.dropdown#user_menu
7 %a.dropdown-toggle(data-toggle="dropdown" href="#")
As you can see above, user_signed_in? is broken. I expect there are several changes needed to move from a 'normal' auto-incrementing ID to a UUID.
For now, I'm just posting the question. I'll take a swing at this later today. If you happen to know how to do this -- or know of a Devise fork, I'd appreciate it.
I've done this in Rails 4 simply by making the id column a uuid data type when creating the table, and no other configuration changes whatsoever. ie. do not create a column named 'uuid', just change the type of the 'id' column to uuid.
Just clear your browser's cookie for the web app (in my case, localhost). The error above is caused because the session was retaining the old user primary key, 1.
After that, things work in my testing. I hope this isn't just luck, it would be a good design if Devise was agnostic about the primary key. (In Devise's code, I saw no use of .id except in some tests.)
2020 answer:
when creating the users table, set the ID as uuid
def change
enable_extension 'pgcrypto' # needed if not already enabled
create_table :users, id: :uuid do |t|
t.string :email,
...

How to: Single Table Inheritance in DataMapper?

I'm learning Sinatra (1.3.2) and chose to use DataMapper (1.2.0) as ORM and an in-memory SQLite (1.3.6) DB to start.
Two models, Books and Downloads, are sharing most attributes, so I looked into declaring a model for STI (Single Table Inheritance) in DataMapper. Reading the docs, this seems a piece of cake thanks to Types::Discriminator.
I abstracted all common ones into DownloadableResource:
class DownloadableResource
include DataMapper::Resource
property :id, Serial
property :created_at, DateTime
property :modified_at, DateTime
property :active, Boolean, default: true
property :position, Integer
property :title, String, required: true
property :url, URI, required: true
property :description, Text, required: true
property :type, Discriminator
end
Following the example, I thought it's just as easy as specifying what needs to be extended:
class Book < DownloadableResource
property :cover_url, URI
property :authors, String, required: true, length: 255
end
and
class Download < DownloadableResource
property :icon_url, URI
end
but this was giving me the following error:
DataObjects::SyntaxError: duplicate column name: id (code: 1, sql state: , query: ALTER TABLE "downloadable_resources" ADD COLUMN "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, uri: sqlite3::memory:?scheme=sqlite&user=&password=&host=&port=&query=&fragment=&adapter=sqlite3&path=:memory:)
while removing the id generated another (obvious) error:
DataMapper::IncompleteModelError: DownloadableResource must have a key to be valid
I got around this by adding include DataMapper::Resource to both Book and Download, and then Book needed a key to be valid, now looking like this:
class Book < DownloadableResource
include DataMapper::Resource
property :id, Serial
property :cover_url, URI
property :authors, String, required: true, length: 255
end
Same goes for Download, but now the issue is:
DataObjects::SyntaxError: duplicate column name: id (code: 1, sql state: , query: ALTER TABLE "books" ADD COLUMN "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, uri: sqlite3::memory:?scheme=sqlite&user=&password=&host=&port=&query=&fragment=&adapter=sqlite3&path=:memory:)
Starting to feel like I'm going in circles, what's the proper way to implement Single Table Inheritance in DataMapper?
PS: I have looked at
DataMapper - Single Table Inheritance and
Ruby Datamapper table inheritance with associations
but I still have this problem.
I would recommend this approach:
module DownloadableResource
def self.included base
base.class_eval do
include DataMapper::Resource
property :created_at, DateTime
property :modified_at, DateTime
property :active, base::Boolean, default: true
property :position, Integer
property :title, String, required: true
property :url, base::URI, required: true
property :description, base::Text, required: true
property :type, base::Discriminator
end
end
end
class Book
include DownloadableResource
property :id, Serial
# other properties
end
class Download
include DownloadableResource
property :id, Serial
# other properties
end