I have a flask with sqlalchemy tied to a postgres db. All components are working with reads fully functional. I have a simple model:
class School(db.Model):
__tablename__ = 'schools'
id = db.Column(db.Integer, db.Sequence('schools_id_seq'), primary_key=True)
name = db.Column(db.String(80))
active = db.Column(db.Boolean)
created = db.Column(db.DateTime)
updated = db.Column(db.DateTime)
def __init__(self, name, active, created, updated):
self.name = name
self.active = active
self.created = created
self.updated = updated
which is working on a postgres table:
CREATE SEQUENCE schools_id_seq;
CREATE TABLE schools(
id int PRIMARY KEY NOT NULL DEFAULT nextval('schools_id_seq'),
name varchar(80) NOT NULL,
active boolean DEFAULT TRUE,
created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER SEQUENCE schools_id_seq OWNED BY schools.id;
when I work with an insert on this table from psql, all is well:
cake=# select nextval('schools_id_seq');
nextval
---------
65
(1 row)
cake=# INSERT INTO schools (id, name, active, created, updated) VALUES (nextval('schools_id_seq'),'Test', True, current_timestamp, current_timestamp);
INSERT 0 1
resulting in:
66 | Test | 0 | t | 2016-08-25 14:12:24.928456 | 2016-08-25 14:12:24.928456
but when I try the same insert from flask, stack trace complains about a duplicate id, but it is using nextval to get that value:
sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "schools_pkey"
DETAIL: Key (id)=(7) already exists.
[SQL: "INSERT INTO schools (id, name, active, created, updated) VALUES (nextval('schools_id_seq'), %(name)s, %(active)s, %(created)s, %(updated)s) RETURNING schools.id"] [parameters: {'active': True, 'name': 'Testomg', 'updated': datetime.datetime(2016, 8, 25, 14, 10, 5, 703471), 'created': datetime.datetime(2016, 8, 25, 14, 10, 5, 703458)}]
Why would the sqlalchemy call to nextval not return the same next val that the same call within the postgres db yields?
UPDATE: #RazerM told me about the echo=true param that I didn't know about. With
app.config['SQLALCHEMY_ECHO']=True
I yielded from a new insert (note that on this try it fetched 10, should be 67):
2016-08-25 14:47:40,127 INFO sqlalchemy.engine.base.Engine select version()
2016-08-25 14:47:40,128 INFO sqlalchemy.engine.base.Engine {}
2016-08-25 14:47:40,314 INFO sqlalchemy.engine.base.Engine select current_schema()
2016-08-25 14:47:40,315 INFO sqlalchemy.engine.base.Engine {}
2016-08-25 14:47:40,499 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
2016-08-25 14:47:40,499 INFO sqlalchemy.engine.base.Engine {}
2016-08-25 14:47:40,594 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
2016-08-25 14:47:40,594 INFO sqlalchemy.engine.base.Engine {}
2016-08-25 14:47:40,780 INFO sqlalchemy.engine.base.Engine show standard_conforming_strings
2016-08-25 14:47:40,780 INFO sqlalchemy.engine.base.Engine {}
2016-08-25 14:47:40,969 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2016-08-25 14:47:40,971 INFO sqlalchemy.engine.base.Engine INSERT INTO schools (id, name, active, created, updated) VALUES (nextval('schools_id_seq'), %(name)s, %(active)s, %(created)s, %(updated)s) RETURNING schools.id
2016-08-25 14:47:40,971 INFO sqlalchemy.engine.base.Engine {'name': 'Testing', 'created': datetime.datetime(2016, 8, 25, 14, 47, 38, 785031), 'active': True, 'updated': datetime.datetime(2016, 8, 25, 14, 47, 38, 785050)}
2016-08-25 14:47:41,064 INFO sqlalchemy.engine.base.Engine ROLLBACK
sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "schools_pkey"
DETAIL: Key (id)=(10) already exists.
[SQL: "INSERT INTO schools (id, name, active, created, updated) VALUES (nextval('schools_id_seq'), %(name)s, %(active)s, %(created)s, %(updated)s) RETURNING schools.id"] [parameters: {'updated': datetime.datetime(2016, 8, 25, 14, 54, 18, 262873), 'created': datetime.datetime(2016, 8, 25, 14, 54, 18, 262864), 'active': True, 'name': 'Testing'}]
Well, solution is simple in that case, it doesn't explain why, because I think we should look at entire environment, which you cannot show us or it will take too long. So try to insert as many records as it will reach 67 and next inserts should apply without any error, because sequence minimum will reach proper value. Of course you can try to add server_default option to id property first:
server_default=db.Sequence('schools_id_seq').next_value()
So
seq = db.Sequence('schools_id_seq')
And in a class:
id = db.Column(db.Integer, seq, server_default=seq.next_value(), primary_key=True)
Sqlalchemy mention about that in this way:
Sequence was originally intended to be a Python-side directive first and foremost so it’s probably a good idea to specify it in this way as well.
Sequences are always incremented, so both your select statement and SQLAlchemy incremented the value.
As stated in Sequence Manipulation Functions:
Advance the sequence object to its next value and return that value. This is done atomically: even if multiple sessions execute nextval concurrently, each will safely receive a distinct sequence value.
If a sequence object has been created with default parameters, successive nextval calls will return successive values beginning with 1. Other behaviors can be obtained by using special parameters in the CREATE SEQUENCE command; see its command reference page for more information.
Important: To avoid blocking concurrent transactions that obtain numbers from the same sequence, a nextval operation is never rolled back; that is, once a value has been fetched it is considered used and will not be returned again. This is true even if the surrounding transaction later aborts, or if the calling query ends up not using the value. For example an INSERT with an ON CONFLICT clause will compute the to-be-inserted tuple, including doing any required nextval calls, before detecting any conflict that would cause it to follow the ON CONFLICT rule instead. Such cases will leave unused "holes" in the sequence of assigned values. Thus, PostgreSQL sequence objects cannot be used to obtain "gapless" sequences.
Related
I absolutely do not understand how to write the net time to the database.
I create a table via sqlalchemy using the time object. Am I doing everything right?
windows = Table(
"windows", meta,
Column("courier_id", Integer, ForeignKey("couriers.courier_id"), nullable=False),
Column("start_time", Time),
Column("end_time", Time)
)
And how do I upload the data now? I did it like this
query = datab.windows.insert().values([1, 09:00, 18:00])
await conn.execute(query)
Аnd there is one more question. How to specify which columns to fill in in insert()
Use a datetime.time() object to specify the time values and pass the .values() as a dict with the column names as the keys:
import datetime
from sqlalchemy import (
create_engine,
Table,
Column,
Integer,
Time,
ForeignKey,
MetaData,
)
engine = create_engine("sqlite:///:memory:", echo=True)
windows = Table(
"windows",
MetaData(),
Column(
"courier_id",
Integer,
# ForeignKey("couriers.courier_id"), # omit for this example
nullable=False,
),
Column("start_time", Time),
Column("end_time", Time),
)
windows.create(engine)
stmt = windows.insert().values(
{
"courier_id": 1,
"start_time": datetime.time(9),
"end_time": datetime.time(18),
}
)
with engine.begin() as conn:
conn.execute(stmt)
"""SQL generated:
2021-03-24 16:57:50,811 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2021-03-24 16:57:50,813 INFO sqlalchemy.engine.Engine INSERT INTO windows (courier_id, start_time, end_time) VALUES (?, ?, ?)
2021-03-24 16:57:50,813 INFO sqlalchemy.engine.Engine [generated in 0.00036s] (1, '09:00:00.000000', '18:00:00.000000')
2021-03-24 16:57:50,813 INFO sqlalchemy.engine.Engine COMMIT
"""
I'm following the example Class::DBI.
I create the cd table like that in my MariaDB database:
CREATE TABLE cd (
cdid INTEGER PRIMARY KEY,
artist INTEGER, # references 'artist'
title VARCHAR(255),
year CHAR(4)
);
The primary key cdid is not set to auto-incremental. I want to use a sequence in MariaDB. So, I configured the sequence:
mysql> CREATE SEQUENCE cd_seq START WITH 100 INCREMENT BY 10;
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT NEXTVAL(cd_seq);
+-----------------+
| NEXTVAL(cd_seq) |
+-----------------+
| 100 |
+-----------------+
1 row in set (0.00 sec)
And set-up the Music::CD class to use it:
Music::CD->columns(Primary => qw/cdid/);
Music::CD->sequence('cd_seq');
Music::CD->columns(Others => qw/artist title year/);
After that, I try this inserts:
# NORMAL INSERT
my $cd = Music::CD->insert({
cdid => 4,
artist => 2,
title => 'October',
year => 1980,
});
# SEQUENCE INSERT
my $cd = Music::CD->insert({
artist => 2,
title => 'October',
year => 1980,
});
The "normal insert" succeed, but the "sequence insert" give me this error:
DBD::mysql::st execute failed: You have an error in your SQL syntax; check the manual that
corresponds to your MariaDB server version for the right syntax to use near ''cd_seq')' at line
1 [for Statement "SELECT NEXTVAL ('cd_seq')
"] at /usr/local/share/perl5/site_perl/DBIx/ContextualFetch.pm line 52.
I think the quotation marks ('') are provoking the error, because when I put the command "SELECT NEXTVAL (cd_seq)" (without quotations) in mysql client it works (see above). I proved all combinations (', ", `, no quotation), but still...
Any idea?
My versions: perl 5.30.3, 10.5.4-MariaDB
The documentation for sequence() says this:
If you are using a database with AUTO_INCREMENT (e.g. MySQL) then you do not need this, and any call to insert() without a primary key specified will fill this in automagically.
MariaDB is based on MySQL. Therefore you do not need the call to sequence(). Use the AUTO_INCREMENT keyword in your table definition instead.
I created a temporary table with sqlalchemy (with an underlying postgres database) that is going to be joined with a database table. However, in some cases when a value is empty '' then postgres throws the error:
failed to find conversion function from unknown to text
SqlAlchemy assembles everything to the following context
[SQL: 'WITH temp_table AS \n(SELECT %(param_1)s AS id, %(param_2)s AS email, %(param_3)s AS phone)\n SELECT campaigns_contact.id, campaigns_contact.email, campaigns_contact.phone \nFROM campaigns_contact JOIN temp_table ON temp_table.id = campaigns_contact.id AND temp_table.email = campaigns_contact.email AND temp_table.phone = campaigns_contact.phone'] [parameters: {'param_1': 83, 'param_2': '', 'param_3': '+1234567890'}]
I assemble the temporary table as follows
stmts = []
for row in import_data:
row_values = [literal(row[value]).label(value) for value in values]
stmts.append(select(row_values))
subquery = union_all(*stmts)
subquery = subquery.cte(name="temp_table")
The problem seems to be the part here
...%(param_2)s AS email...
which after replacing the param_2 results in
...'' AS email...
which will cause the error mentioned above.
One way to solve the issue is to perform a cast
...''::text AS email...
However, I don't know how to perform ::text cast with sqlalchemy!?
I have this very weird issue with One2many field.
First let me explain you the scenario...
I have a One2many field in sale.order.line, below code will explain the structure better
class testModule(models.Model):
_name = 'test.module'
name = fields.Char()
class testModule2(models.Model):
_name = 'test.module2'
location_id = fields.Many2one('test.module')
field1 = fields.Char()
field2 = fields.Many2one('sale.order.line')
class testModule3(models.Model):
_inherit = 'sale.order.line'
test_location = fields.One2many('test.module2', 'field2')
CASE 1:
Now what is happening is that when i create a new sales order, i select the partner_id and then add a sale.order.line and inside this line i add the One2many field test_location and then i save.
CASE 2:
Create new sales order, select partner_id then add sale.order.line and inside the sale.order.line add the test_location line [close the sales order line window]. Now after the entry before hitting save i change a field say partner_id and then click save.
CASE 3:
this case is same as case 2 but with the addition that i again change the partner_id field [changes made total 2 times first of case2 and then now], then i click on save.
RESULTS
CASE 1 works fine.
CASE 2 has a issue of
odoo.sql_db: bad query: INSERT INTO "test_module2" ("id", "field2", "field1", "location_id", "create_uid", "write_uid", "create_date", "write_date") VALUES(nextval('test_module2_id_seq'), 27, 'asd', ARRAY[1, '1'], 1, 1, (now() at time zone 'UTC'), (now() at time zone 'UTC')) RETURNING id
ProgrammingError: column "location_id" is of type integer but expression is of type integer[]
LINE 1: ...VALUES(nextval('test_module2_id_seq'), 27, 'asd', ARRAY[1, '...
now for this case i put a debugger on create/write method of sale.order.line to see waht the values are getting passed..
values = {u'product_uom': 1, u'sequence': 0, u'price_unit': 885, u'product_uom_qty': 1, u'qty_invoiced': 0, u'procurement_ids': [[5]], u'qty_delivered': 0, u'qty_to_invoice': 0, u'qty_delivered_updateable': False, u'customer_lead': 0, u'analytic_tag_ids': [[5]], u'state': u'draft', u'tax_id': [[5]], u'test_location': [[5], [0, 0, {u'field1': u'asd', u'location_id': [1, u'1']}]], 'order_id': 20, u'price_subtotal': 885, u'discount': 0, u'layout_category_id': False, u'product_id': 29, u'price_total': 885, u'invoice_status': u'no', u'name': u'[CARD] Graphics Card', u'invoice_lines': [[5]]}
in the above values location_id is getting passed like u'location_id': [1, u'1']}]] which is not correct...so for this i correct the issue in code and the update the values and pass that...
CASE 3
if the user changes the field say 2 or more than 2 times then the values are
values = {u'invoice_lines': [[5]], u'procurement_ids': [[5]], u'tax_id': [[5]], u'test_location': [[5], [1, 7, {u'field1': u'asd', u'location_id': False}]], u'analytic_tag_ids': [[5]]}
here
u'location_id': False
MULTIPLE CASE
if the user does case 1 the on the same record does case 2 or case 3 then sometimes the line will be saved as field2 = Null or False in the database other values like location_id and field1 will have data but not field2
NOTE: THIS HAPPENS WITH ANY FIELD NOT ONLY PARTNER_ID FIELD ON HEADER LEVEL OF SALE ORDER
I tried debugging myself but couldn't find the reason why this is happening .
I try start a sequence with initial number in tenants, but only public schema got this.
Take a look at my migration:
class CreateDisputes < ActiveRecord::Migration[5.0]
def change
create_table :disputes, id: :uuid do |t|
...
t.integer :code
...
end
execute %{
CREATE SEQUENCE disputes_code_seq INCREMENT BY 1
NO MINVALUE NO MAXVALUE
START WITH 1001 CACHE 1
OWNED BY disputes.code;
ALTER TABLE ONLY disputes
ALTER COLUMN code SET DEFAULT nextval('disputes_code_seq'::regclass);
}
...
end
end
Thanks!
I am no expert in apartement gem. But, apartment is not creating the disputes_code_seq in the tenant's schema.
The workaround for this is to uncomment the following line in config/initializers/apartment.rb
# Apartment can be forced to use raw SQL dumps instead of schema.rb for creating new schemas.
# Use this when you are using some extra features in PostgreSQL that can't be respresented in
# schema.rb, like materialized views etc. (only applies with use_schemas set to true).
# (Note: this option doesn't use db/structure.sql, it creates SQL dump by executing pg_dump)
#
config.use_sql = true
With config.user_sql set to true, Apartment migration will create the sequence for tenant. Here is the log(s) from migration and rails console for reference.
Following is the migration log
ubuntu#ubuntu-xenial:~/devel/apartment/testseq$ rails db:migrate
== 20170224161015 CreateDisputes: migrating ===================================
-- create_table(:disputes)
-> 0.0035s
-- execute("\n CREATE SEQUENCE disputes_code_seq INCREMENT BY 1\n NO MINVALUE NO MAXVALUE\n START WITH 1001 CACHE 1\n OWNED BY disputes.code;\n\n ALTER TABLE ONLY disputes\n ALTER COLUMN code SET DEFAULT nextval('disputes_code_seq'::regclass);\n ")
-> 0.0012s
== 20170224161015 CreateDisputes: migrated (0.0065s) ==========================
[WARNING] - The list of tenants to migrate appears to be empty. This could mean a few things:
1. You may not have created any, in which case you can ignore this message
2. You've run `apartment:migrate` directly without loading the Rails environment
* `apartment:migrate` is now deprecated. Tenants will automatically be migrated with `db:migrate`
Note that your tenants currently haven't been migrated. You'll need to run `db:migrate` to rectify this.
Following is the log of tenant creation and adding a row to disputes
irb(main):001:0> Apartment::Tenant.create('tenant2')
<output snipped for brevity>
irb(main):005:0> Apartment::Tenant.switch!('tenant2')
=> "\"tenant2\""
irb(main):006:0> d = Dispute.new
=> #<Dispute id: nil, code: nil, created_at: nil, updated_at: nil>
irb(main):007:0> d.save
(0.2ms) BEGIN
SQL (0.6ms) INSERT INTO "disputes" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", 2017-02-25 03:09:49 UTC], ["updated_at", 2017-02-25 03:09:49 UTC]]
(0.6ms) COMMIT
=> true
irb(main):008:0> d.reload
Dispute Load (0.3ms) SELECT "disputes".* FROM "disputes" WHERE "disputes"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
=> #<Dispute id: 1, code: 1001, created_at: "2017-02-25 03:09:49", updated_at: "2017-02-25 03:09:49">
As you can see in the following log , code is starting with sequence numbers.
irb(main):009:0> d = Dispute.new
=> #<Dispute id: nil, code: nil, created_at: nil, updated_at: nil>
irb(main):010:0> d.save
(0.3ms) BEGIN
SQL (0.6ms) INSERT INTO "disputes" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", 2017-02-25 03:11:13 UTC], ["updated_at", 2017-02-25 03:11:13 UTC]]
(0.5ms) COMMIT
=> true
irb(main):011:0> d.reload
Dispute Load (0.5ms) SELECT "disputes".* FROM "disputes" WHERE "disputes"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
=> #<Dispute id: 2, code: 1002, created_at: "2017-02-25 03:11:13", updated_at: "2017-02-25 03:11:13">