1 (in-package :meta-model
)
3 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
6 (export 'def-meta-model-from-table
)
7 (export 'def-meta-models
)
8 (export 'def-view-class
/meta
)
9 (export 'list-base-classes
)
11 (defparameter *clsql-base-classes
* (list) )
13 (defmethod list-base-classes ((type (eql :clsql
)))
16 (defmethod def-base-class-expander ((model meta-model-class
) (base-type (eql :clsql
)) (name t
) (args t
))
17 `(def-view-class ,name
()
18 ,(meta-model.metadata model
)))
20 (defmethod def-base-class-expander :after
((model meta-model-class
) (base-type (eql :clsql
)) (name t
) (args t
))
21 (unless (member name
*clsql-base-classes
*)
22 (setf *clsql-base-classes
* (cons name
*clsql-base-classes
*))))
24 (defparameter *sql-type-map
* '((:INT4 integer
) (:TEXT string
) (:VARCHAR string
) (:TIMESTAMP clsql-sys
::wall-time
) (:NUMERIC number
)(:BYTEA string
)))
26 (defun gen-type (table column
)
30 (list-attribute-types table
)
34 (defun sql->sym
(name &optional
(package nil
))
36 (string-upcase (substitute #\-
#\_ x
))))
38 (intern (xform (string name
)) package
)
39 (intern (xform (string name
))))))
41 (defun table->slots
(table pkey
)
45 :accessor
,(sql->sym col
)
46 :initarg
,(sql->sym col
"KEYWORD")
47 :type
,(gen-type table col
)
49 ,(if (equalp col pkey
)
52 (list-attributes table
)))
54 (defun view-class-definition-list ()
55 (mapcar #'(lambda (x) `(def-meta-model-from-table ,x
))
58 (defmacro def-meta-models
()
59 (let ((defs (view-class-definition-list)))
68 (dolist (row (get-pkeys-query))
69 (setf keys
(acons (car row
) (list (cadr row
)) keys
)))
72 (defun get-pkeys-query()
74 "SELECT pg_class.relname, pg_attribute.attname, pg_catalog.quote_ident(conname) AS constraint_n
75 , pg_catalog.pg_get_indexdef(d.objid) AS constraint_definition
77 WHEN contype = 'p' THEN
81 END as constraint_type
83 pg_class, pg_attribute,
84 pg_catalog.pg_constraint AS c
85 JOIN pg_catalog.pg_depend AS d ON (d.refobjid = c.oid)
86 WHERE contype IN ('p', 'u')
88 and conrelid = pg_class.oid
89 and pg_attribute.attnum = ANY (c.conkey)
90 and pg_attribute.attrelid = pg_class.oid"))
92 ;;here is how this works
93 ;;from the postgres system tables we get
94 ;;list of all the has-a relationships.
95 ;;the inverse of a has-a is an implicit has-many
96 ;;and any relation having more than one foreign key
97 ;;is a join table hosting a many-to-many relationship
99 (defun get-fkey-explosions ()
100 (let ((key-table (get-fkey-explosions-query))
102 (dolist (row key-table
)
103 (setf row
(mapcar #'(lambda (x)
106 ;;this one does the has-a
107 (setf keys
(acons (car row
) (gen-has-a row
)
109 ;;the inverse of the previous represents a has-many.
111 (acons (fourth row
) (gen-has-many row
)
116 (remove-if #'(lambda (r) (or (not (equal (car row
) (car r
)))
117 (equal (last row
) (last r
))))
118 (mapcar #'(lambda (x)
119 (mapcar #'sql-
>sym x
))
121 (setf keys
(acons (fourth row
)
122 (gen-many-to-many mrow
(third row
) (second row
))
127 (defun get-fkey-explosions-query ()
128 ;;these query's are a mess, i don't even know how they work :)
130 SELECT pg_class.relname,
131 pg_attribute.attname,
139 WHERE pg_class.relname in (select tablename from pg_tables where schemaname = 'public')
140 AND pg_class.oid = pg_constraint.conrelid
141 AND pg_attribute.attnum = ANY (pg_constraint.conkey)
142 AND pg_attribute.attrelid = pg_class.oid
143 AND f.oid = confrelid
144 AND fa.attrelid = f.oid
145 AND fa.attnum = ANY (pg_constraint.confkey)"))
148 ;; i chose keyword args here so as to make the code more understandable.
149 ;; it didn't really work.
150 (defun gen-join-slot (&key name home-key foreign-key join-class
(set nil
))
152 :accessor
,(intern name
)
154 :db-info
(:join-class
,join-class
156 :foreign-key
,foreign-key
159 (defun gen-has-a (row)
162 (format nil
"~A->~A" (string (car row
))(string (second row
)))
163 :home-key
(second row
)
164 :foreign-key
(third row
)
165 :join-class
(fourth row
)))
167 (defun gen-has-many (row)
170 (format nil
"~A->~A" (string (car row
))(string (second row
)))
171 :home-key
(third row
)
172 :foreign-key
(second row
)
173 :join-class
(car row
)
176 (defun gen-many-to-many (row home-key foreign-key
)
177 (let ((name (sql->sym
(string-upcase (format nil
"~A->~A" (string (car row
)) (string (second row
)))))))
178 (setf row
(mapcar #'sql-
>sym row
))
182 :db-info
(:join-class
,(car row
)
184 :foreign-key
,foreign-key
188 (defmethod update-records-from-instance :before
((view clsql
::standard-db-object
) &key database
)
189 (declare (ignorable database
))
190 (labels ((sym->sql
(sym) (string-downcase (substitute #\_
#\-
(string sym
))))
191 (get-def (slot) (caar (query
192 (format nil
"SELECT DISTINCT adsrc from pg_attrdef join pg_attribute on attnum = adnum where adrelid = (select oid from pg_class where relname = '~A') and attname = '~A'" (sym->sql
(class-name (class-of view
))) (sym->sql slot
)))))
193 (get-default-value (slot) (caar (query (format nil
"SELECT ~A" (get-def slot
))))))
195 (dolist (slot (list-slots view
))
196 (when (and (primary-key-p view slot
)
197 (or (not (slot-boundp view slot
))
198 (equal (slot-value view slot
) nil
)))
199 (setf (slot-value view slot
) (get-default-value slot
))))))
203 (defmacro def-view-class
/meta
(name supers slots
&rest args
)
205 (let* ((m (def-meta-model model-name
,supers
,slots
,args
))
206 (i (make-instance m
)))
207 (prog1 (eval (def-base-class-expander i
:clsql
',name
',args
))
208 (defmethod meta-model.metadata
((self ,name
))
209 (meta-model.metadata i
))))))
212 (defmacro def-view-class
/table
(table &optional name
)
213 "takes the name of a table as a string and
214 creates a clsql view-class"
215 (let* ((pkey (cadr (assoc table
(get-pkeys) :test
#'equalp
)))
216 (table-slots (table->slots table pkey
))
219 (dolist (exp (get-fkey-explosions))
220 (when (equalp (car exp
) (sql->sym table
))
221 (setf slots
(cons (cdr exp
) slots
))))
223 `(def-view-class/meta
,(if name name
(sql->sym table
))
225 ,(append table-slots join-slots
))))