Skip to content

Commit

Permalink
P3Constraint hierarchy moved to P3 repository
Browse files Browse the repository at this point in the history
P3ClientTest>>#testConstraints added
  • Loading branch information
svenvc committed Jan 2, 2024
1 parent 8f9ee23 commit aba118a
Show file tree
Hide file tree
Showing 6 changed files with 425 additions and 0 deletions.
42 changes: 42 additions & 0 deletions P3-Tests/P3ClientTest.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,48 @@ P3ClientTest >> testConnection [
self assert: client isWorking.
]

{ #category : #tests }
P3ClientTest >> testConstraints [
| statement constraints constraint |
client execute: 'DROP TABLE IF EXISTS orders'.
client execute: 'DROP TABLE IF EXISTS suppliers'.
client execute: 'CREATE TABLE suppliers (id INTEGER, name TEXT)'.
client execute: 'CREATE TABLE orders (id INTEGER, description TEXT, supplier_id INTEGER)'.
client execute: 'ALTER TABLE suppliers ADD CONSTRAINT suppliers_pkey PRIMARY KEY (id)'.
client execute: 'ALTER TABLE orders ADD CONSTRAINT orders_pkey PRIMARY KEY (id)'.
client execute: 'ALTER TABLE orders ADD CONSTRAINT orders_suppliers_fkey FOREIGN KEY (supplier_id) REFERENCES suppliers (id)'.
statement := client format: 'INSERT INTO suppliers (id, name) VALUES ($1, $2)'.
statement executeBatch: #((1 'Foo')(2 'Bar')).
statement := client format: 'INSERT INTO orders (id, description, supplier_id) VALUES ($1, $2, $3)'.
statement executeBatch: #((1 'AAA' 1)(2 'BBB' 1)(3 'CCC' 2)).
constraints := P3Constraint allForTable: 'suppliers' in: 'public' using: client.
constraint := constraints
detect: [ :each | each constraintName = 'suppliers_pkey' ]
ifNone: [ self fail ].
self assert: constraint isPrimaryKey.
self assert: constraint tableName equals: 'suppliers'.
self assert: constraint constraintColumns equals: #(id).
constraints := P3Constraint allForTable: 'orders' in: 'public' using: client.
constraint := constraints
detect: [ :each | each constraintName = 'orders_pkey' ]
ifNone: [ self fail ].
self assert: constraint isPrimaryKey.
self assert: constraint tableName equals: 'orders'.
self assert: constraint constraintColumns equals: #(id).
constraint := constraints
detect: [ :each | each constraintName = 'orders_suppliers_fkey' ]
ifNone: [ self fail ].
self assert: constraint isForeignKey.
self assert: constraint tableName equals: 'orders'.
self assert: constraint constraintColumns equals: #(supplier_id).
self assert: constraint foreignKeyTable equals: 'suppliers'.
self assert: constraint foreignKeyColumns equals: #(id).
constraints := P3Constraint referencingConstraintNamesForTable: 'suppliers' in: 'public' using: client.
self assert: constraints equals: #(('orders' 'orders_suppliers_fkey')).
client execute: 'DROP TABLE orders'.
client execute: 'DROP TABLE suppliers'
]

{ #category : #tests }
P3ClientTest >> testConvenienceMetaAccess [
self deny: client listDatabases isEmpty.
Expand Down
53 changes: 53 additions & 0 deletions P3/P3CheckConstraint.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"
I am P3CheckConstraint.
I am a P3Constraint.
I implement SQL CHECK.
I know my check clause.
"
Class {
#name : #P3CheckConstraint,
#superclass : #P3Constraint,
#instVars : [
'checkClause'
],
#category : #'P3-Support'
}

{ #category : #accessing }
P3CheckConstraint class >> handlesType: type [
^ type = 'CHECK'
]

{ #category : #accessing }
P3CheckConstraint >> checkClause [
^ checkClause
]

{ #category : #accessing }
P3CheckConstraint >> checkClause: anObject [
checkClause := anObject
]

{ #category : #accessing }
P3CheckConstraint >> constraintType [
^ 'CHECK'
]

{ #category : #accessing }
P3CheckConstraint >> loadDetailsUsing: client [
| statement result |
super loadDetailsUsing: client.
statement := client format: 'SELECT check_clause FROM information_schema.check_constraints WHERE constraint_schema = $1 AND constraint_name = $2'.
result := statement query: { self constraintSchema . self constraintName }.
self checkClause: result firstFieldOfFirstRecord
]

{ #category : #accessing }
P3CheckConstraint >> sqlDescription [
^ String streamContents: [ :out |
out nextPutAll: 'CHECK'.
self checkClause ifNotNil: [ :clause |
out space; nextPutAll: clause ] ]
]
202 changes: 202 additions & 0 deletions P3/P3Constraint.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
"
I am P3Constraint.
I am an abstract class.
I represent an SQL constraint.
I have
- a name
- a schem:table that I am defined on
- a list of columns on which I am applicable
I can reproduce my SQL definition.
Use me to get all constraints for a given table.
My concrete subclasses implement actual constraint types.
"
Class {
#name : #P3Constraint,
#superclass : #Object,
#instVars : [
'constraintSchema',
'constraintName',
'tableSchema',
'tableName',
'isDeferrable',
'initiallyDeferred',
'enforced',
'constraintColumns'
],
#category : #'P3-Support'
}

{ #category : #accessing }
P3Constraint class >> allForTable: tableName in: schemaName using: client [
| statement result specificClass constraint |
statement := client format: 'SELECT constraint_schema, constraint_name, constraint_type, is_deferrable, initially_deferred, enforced FROM information_schema.table_constraints WHERE table_schema = $1 AND table_name= $2'.
result := statement query: { schemaName . tableName }.
^ result data collect: [ :row |
specificClass := self subclasses
detect: [ :each | each handlesType: row third ]
ifNone: [ self error: 'unknown contraint type' ].
constraint := specificClass new.
constraint
tableSchema: schemaName;
tableName: tableName;
constraintSchema: row first;
constraintName: row second;
isDeferrable: row fourth = 'YES';
initiallyDeferred: row fifth = 'YES';
enforced: row = 'YES'.
constraint loadDetailsUsing: client.
constraint ]
]

{ #category : #accessing }
P3Constraint class >> handlesType: type [
self subclassResponsibility
]

{ #category : #accessing }
P3Constraint class >> referencingConstraintNamesForTable: tableName in: schemaName using: client [
"Return (table_name, constraint_name) pairs where the given schemaName:tableName is referenced.
Do not return the actual constraint object as this might be costly to compute."

| statement result |
statement := client format: 'SELECT tc.table_name, ctu.constraint_name
FROM information_schema.constraint_table_usage AS ctu, information_schema.table_constraints AS tc
WHERE ctu.table_schema = $1 AND ctu.table_name = $2 AND ctu.constraint_name = tc.constraint_name AND tc.constraint_type = ''FOREIGN KEY'''.
result := statement query: { schemaName . tableName }.
^ result data
]

{ #category : #accessing }
P3Constraint >> constraintColumns [
^ constraintColumns
]

{ #category : #accessing }
P3Constraint >> constraintColumns: anObject [
constraintColumns := anObject
]

{ #category : #accessing }
P3Constraint >> constraintName [
^ constraintName
]

{ #category : #accessing }
P3Constraint >> constraintName: anObject [
constraintName := anObject
]

{ #category : #accessing }
P3Constraint >> constraintSchema [
^ constraintSchema
]

{ #category : #accessing }
P3Constraint >> constraintSchema: anObject [
constraintSchema := anObject
]

{ #category : #accessing }
P3Constraint >> constraintType [
self subclassResponsibility
]

{ #category : #accessing }
P3Constraint >> enforced [
^ enforced
]

{ #category : #accessing }
P3Constraint >> enforced: anObject [
enforced := anObject
]

{ #category : #accessing }
P3Constraint >> initiallyDeferred [
^ initiallyDeferred
]

{ #category : #accessing }
P3Constraint >> initiallyDeferred: anObject [
initiallyDeferred := anObject
]

{ #category : #testing }
P3Constraint >> isCheck [
^ self constraintType = 'CHECK'
]

{ #category : #accessing }
P3Constraint >> isDeferrable [
^ isDeferrable
]

{ #category : #accessing }
P3Constraint >> isDeferrable: anObject [
isDeferrable := anObject
]

{ #category : #testing }
P3Constraint >> isForeignKey [
^ self constraintType = 'FOREIGN KEY'
]

{ #category : #testing }
P3Constraint >> isPrimaryKey [
^ self constraintType = 'PRIMARY KEY'
]

{ #category : #testing }
P3Constraint >> isUnique [
^ self constraintType = 'UNIQUE'
]

{ #category : #accessing }
P3Constraint >> loadDetailsUsing: client [
| statement result |
statement := client format: 'SELECT column_name FROM information_schema.key_column_usage WHERE constraint_schema = $1 AND constraint_name = $2'.
result := statement query: { self constraintSchema . self constraintName }.
self constraintColumns: result firstColumnData
]

{ #category : #accessing }
P3Constraint >> printOn: stream [
super printOn: stream.
stream nextPut: $(.
self constraintName ifNotNil: [ :name |
stream nextPutAll: name ].
self sqlDescription ifNotNil: [ :description |
self constraintName ifNotNil: [ stream space ].
stream nextPutAll: description ].
stream nextPut: $)
]

{ #category : #accessing }
P3Constraint >> sqlDescription [
self subclassResponsibility
]

{ #category : #accessing }
P3Constraint >> tableName [
^ tableName
]

{ #category : #accessing }
P3Constraint >> tableName: anObject [
tableName := anObject
]

{ #category : #accessing }
P3Constraint >> tableSchema [
^ tableSchema
]

{ #category : #accessing }
P3Constraint >> tableSchema: anObject [
tableSchema := anObject
]
76 changes: 76 additions & 0 deletions P3/P3ForeignKeyConstraint.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"
I am P3ForeignKeyConstraint.
I am a P3Constraint.
I implement SQL FOREIGN KEY.
I know to table and columns that I reference.
"
Class {
#name : #P3ForeignKeyConstraint,
#superclass : #P3Constraint,
#instVars : [
'foreignKeyTable',
'foreignKeyColumns'
],
#category : #'P3-Support'
}

{ #category : #accessing }
P3ForeignKeyConstraint class >> handlesType: type [
^ type = 'FOREIGN KEY'
]

{ #category : #accessing }
P3ForeignKeyConstraint >> constraintType [
^ 'FOREIGN KEY'
]

{ #category : #accessing }
P3ForeignKeyConstraint >> foreignKeyColumns [
^ foreignKeyColumns
]

{ #category : #accessing }
P3ForeignKeyConstraint >> foreignKeyColumns: anObject [
foreignKeyColumns := anObject
]

{ #category : #accessing }
P3ForeignKeyConstraint >> foreignKeyTable [
^ foreignKeyTable
]

{ #category : #accessing }
P3ForeignKeyConstraint >> foreignKeyTable: anObject [
foreignKeyTable := anObject
]

{ #category : #accessing }
P3ForeignKeyConstraint >> loadDetailsUsing: client [
| statement result |
super loadDetailsUsing: client.
statement := client format: 'SELECT column_name FROM information_schema.constraint_column_usage WHERE constraint_schema = $1 AND constraint_name = $2'.
result := statement query: { self constraintSchema . self constraintName }.
self foreignKeyColumns: result firstColumnData.
statement := client format: 'SELECT table_name FROM information_schema.constraint_table_usage WHERE constraint_schema = $1 AND constraint_name = $2'.
result := statement query: { self constraintSchema . self constraintName }.
self foreignKeyTable: result firstFieldOfFirstRecord
]

{ #category : #accessing }
P3ForeignKeyConstraint >> sqlDescription [
^ String streamContents: [ :out |
out nextPutAll: 'FOREIGN KEY'.
(self constraintColumns isEmptyOrNil
or: [ self foreignKeyColumns isEmptyOrNil
or: [ self foreignKeyTable isNil ] ])
ifFalse: [
out nextPutAll: ' (';
nextPutAll: ($, join: self constraintColumns);
nextPutAll: ') REFERENCES ';
nextPutAll: self foreignKeyTable;
nextPut: $(;
nextPutAll: ($, join: self foreignKeyColumns);
nextPut: $) ] ]
]
Loading

0 comments on commit aba118a

Please sign in to comment.