How to implement viewclass with foreign keys also used as keys - common-lisp

Assume I have SQL table definitions like this
CREATE TABLE X (
id integer not null,
value character varying,
PRIMARY KEY (id)
);
CREATE TABLE Y (
start integer not null,
end integer not null,
value character vartying,
PRIMARY KEY (start,end),
FOREIGN KEY(start)
REFERENCES X (id)
ON DELETE CASCADE,
FOREIGN KEY(end)
REFERENCES X (id)
ON DELETE CASCADE
);
The first table is straight forward
(clsql:def-view-class x ()
((id
:db-kind :key
:db-type integer
:db-constraints :not-null
:reader id)
(value
:initarg :value
:initform nil
:db-type (string 255)
:reader value))
(:base-table xes))
But I do not know how to do the second as I can either define db-kind :key or :join. Further I did not find any specifications concerning ON DELETE ...
Is it even possible to implement the given table combination using the clsql oop model and if yes how?

I think the biggest problem is the declaration of the composite primary key (i.e. PRIMARY KEY (start, end)). Setting up the join with a non-composite primary key constraint is straight-forward:
(clsql:def-view-class y ()
((start
:db-kind :join
:db-info (:join-class x
:home_key y_start
:foreign_key id
:set nil)
:db-type integer
:db-constraints :primary-key
:reader start)
((end
:db-kind :base
:db-type integer
:db-constraints :not-null
:reader start)
(value
:initarg :value
:initform nil
:db-type (string 255)
:reader value))
(:base-table yes))
In principle, one would want to setup the composite key as a class option, but this is currently not supported by CL-SQL's OODML. Neither is there support for expressing ON DELETE behavior.
If you need both of these, you should be able to do so by falling back to execute-commands.

Related

How can I convert Record to Row at type Level?

I have a record, I want to pass a partial record to a function which will update the current record.
type Person = {name :: String, age :: Int }
updatePerson :: forall fullRec partialRec.
Union fullRec partialRec fullRec
=> Record fullRec
-> Record partialRec
-> Record fullRec
updatePerson a b = ....
I can use merge and union in purescript-record, but my use case is different. I will call a foreign function with that argument which will update the global Person object which in, let's say, in the window object... so the function will only receive the partial record to be updated.
I want to do something like
foreign import updatePersonInWindow :: forall a. {|a} -> Effect Unit
updatePerson' :: forall fullRec partialRec.
Union Person partialRec Person
=> Record partialRec
-> Effect Unit
updatePerson' rec = updatePersonInWindow rec
The above one will fail, because Person is a Type, not of Row Kind...
Currently i'm calling directly calling the foreign function, so one can pass any field to it, even though it's not inside the Person Record. How can I do it in type safe way?
PS: I could do
Union (name :: String, age :: Int) partialRec (name :: String, age :: Int)
But I'm looking for an answer that is short, without any boilerplate.

Modify a row that is under a foreign key constraint

I have a table with integer columns as below. The purpose of the bexParentID and bexParentTypeID columns is to be foreign-key constrained to other rows in the same table, i.e. (bexParentID,bexParentTypeID) has a composite FK constraint to (bexID,bexTypeID) in the same table.
This is the create script:
CREATE TABLE [main].[Boolean_Expressions](
[bexID] INTEGER PRIMARY KEY NOT NULL,
[bexTypeID] INTEGER NOT NULL,
[bexParentID] INTEGER NOT NULL,
[bexParentTypeID] INTEGER NOT NULL,
FOREIGN KEY([bexParentID], [bexParentTypeID]) REFERENCES [Boolean_Expressions]([bexID], [bexTypeID]),
UNIQUE([bexID], [bexTypeID]);
table design
Here is an example of data that might appear in this table.
data
How do I update the Type of a row (call it A) that has foreign-key constraints from rows B, C, D... on it? It is a violation to update the ParentType of the rows B, C, D..., and it is also a violation to update Type in A first.
I can only think of 'pointing' B, C, D.... to another row by changing their ParentID and ParentType to point to an altogether different row (call it X), then changing A's Type, then 'pointing' B, C, D... back to A.
Add ON UPDATE CASCADE to the FK constraint:
FOREIGN KEY([bexParentID], [bexParentTypeID]) REFERENCES [Boolean_Expressions]([bexID], [bexTypeID]) ON UPDATE CASCADE
Effect: it is sufficient to update the parent row; all dependent child rows will be updated to have the same value.
Two options that could be simpler.
a) Turn Foreign keys Off before the update and then On after the update. Using 'PRAGMA foreign_keys = off' and 'PRAGMA foreign_keys = on'
= false (off) or = true (on) or = 0 (off) or = 1 (on) (any value greater then 0 is on)
note cannot be effectively used (aka it's a NOP) inside a transaction.
e.g.
PRAGMA foreign_keys = off;
UPDATE ....;
PRAGMA foreign_keys = on;
or
b) Use deferred Foreign Keys, that is include:-
DEFERRABLE INITIALLY DEFERRED
In the Foreign Key definition(s).
e.g.
bexParentID INTEGER NOT NULL REFERENCES mytable1(bexID) DEFERRABLE INITIALLY DEFERRED
The Foreign Key conflict is checked when the transaction is committed. So the changes would need to be done inside a transaction. e.g.
BEGIN TRASNACTION;
UPDATE ....;
END TRANSACTION;

How to prevent recursive hierarchies?

It would be helpful if the following two tables had a specific constraint:
-- Table: privilege_group
CREATE TABLE privilege_group (
privilege_group_id integer NOT NULL CONSTRAINT privilege_group_pk PRIMARY KEY AUTOINCREMENT,
name text NOT NULL,
CONSTRAINT privilege_group_name UNIQUE (name)
);
-- Table: privilege_relationship
CREATE TABLE privilege_relationship (
privilege_relationship_id integer NOT NULL CONSTRAINT privilege_relationship_pk PRIMARY KEY AUTOINCREMENT,
parent_id integer NOT NULL,
child_id integer NOT NULL,
CONSTRAINT privilege_relationship_parent_child UNIQUE (parent_id, child_id),
CONSTRAINT privilege_relationship_parent_id FOREIGN KEY (parent_id)
REFERENCES privilege_group (privilege_group_id),
CONSTRAINT privilege_relationship_child_id FOREIGN KEY (child_id)
REFERENCES privilege_group (privilege_group_id),
CONSTRAINT privilege_relationship_check CHECK (parent_id != child_id)
);
Each parent can have multiple children, and each child can have multiple parents. Processing relationships can be done outside of the database, but is there a way within the database to prevent recursive relationships?
My research shows that it is possible to use CREATE TRIGGER and to use a RAISE function to prevent something from completing, but the possibility of internal depth-first or breadth-first searches in SQLite3 is unknown to me.

sqlite multiple unique isn't work

I have a table:
CREATE TABLE IF NOT EXISTS city_recent
(_id INTEGER PRIMARY KEY AUTOINCREMENT,
city_id INTEGER NOT NULL,
language BOOL NOT NULL,
type BOOL NOT NULL,
FOREIGN KEY (city_id) REFERENCES city(_id),
UNIQUE(city_id, type) ON CONFLICT IGNORE)
But unique don't work:
I have tested your code and it works as expected (test shown below). Most likely what has happened is that the table was created beforehand without the UNIQUE constraint. Try removing IF NOT EXISTS to confirm.
>>> import sqlite3
>>> con = sqlite3.connect(':memory:')
>>> con.execute('''CREATE TABLE IF NOT EXISTS city_recent
... (_id INTEGER PRIMARY KEY AUTOINCREMENT,
... city_id INTEGER NOT NULL,
... language BOOL NOT NULL,
... type BOOL NOT NULL,
... FOREIGN KEY (city_id) REFERENCES city(_id),
... UNIQUE(city_id, type) ON CONFLICT IGNORE);''')
<sqlite3.Cursor object at 0x01298FA0>
>>> con.execute('insert into city_recent(city_id,language,type) values (0,0,1);')
<sqlite3.Cursor object at 0x0129F120>
>>> con.execute('insert into city_recent(city_id,language,type) values (0,0,1);')
<sqlite3.Cursor object at 0x01298FA0>
>>> con.execute('select * from city_recent').fetchall()
[(1, 0, 0, 1)] # -> note that there is only one row in the table

How do I insert rows containing Timestamp values, using clojure.java.jdbc?

I'm trying to use clojure.java.jdbc to insert rows into a database. (The database in question is sqlite).
I can create a table like this:
(def db {:classname "org.sqlite.JDBC"
:subprotocol "sqlite"
:subname "/path/to/my/database"})
(with-connection db (create-table :foo [:bar :int]
[:baz :int]
[:timestamp :datetime]))
And this works. But if I try to insert a row into the database, this fails:
(with-connection db (insert-rows :foo
[1 2 (java.sql.Timestamp. (.getTime (java.util.Date.)))]))
Giving an exception: assertion failure: param count (3) != value count (6).
But if I omit the timestamp field from the table definition and insert-rows operation, there isn't a problem. So what am I doing wrong with the timestamp?
(def sqllite-settings
{
:classname "org.sqlite.JDBC"
:subprotocol "sqlite"
:subname "test.db"
}
)
(with-connection sqllite-settings
(create-table :foo
[:bar :int]
[:baz :int]
[:timestamp :datetime]))
(with-connection sqllite-settings (insert-rows :foo
[1 2 (java.sql.Timestamp. (.getTime (java.util.Date.)))]))
(with-connection sqllite-settings
(with-query-results rs ["select * from foo"] (doall rs)))
returned the expected:
({:bar 1, :baz 2, :timestamp 1311565709390})
I am using clojure.contrib.sql
(use 'clojure.contrib.sql)
And the SQLLite driver from here: http://www.zentus.com/sqlitejdbc/
Can you try if contrib.sql works for you ?

Resources