Queries improved, all tests in adl.to-hugsql-queries-test pass.

This commit is contained in:
Simon Brooke 2018-03-21 10:32:02 +00:00
parent 5cf0a4cbed
commit dcbe9ee01b
4 changed files with 491 additions and 309 deletions

View file

@ -11,7 +11,7 @@
<!-- Copyright: (c) 2007 Cygnet Solutions --> <!-- Copyright: (c) 2007 Cygnet Solutions -->
<!-- --> <!-- -->
<!-- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --> <!-- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
<!-- <!--
$Revision: 1.5 $ $Revision: 1.5 $
--> -->
@ -24,8 +24,8 @@
<!-- Before we start: some useful definitions --> <!-- Before we start: some useful definitions -->
<!-- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --> <!-- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
<!-- some basic character entities inherited from HTML. Actually we probably ought to <!-- some basic character entities inherited from HTML. Actually we probably ought to
import all the HTML4 character entity files, and possibly the HTML4 Strict DTD (so import all the HTML4 character entity files, and possibly the HTML4 Strict DTD (so
that we can allow HTML block level entities within content elements --> that we can allow HTML block level entities within content elements -->
<!ENTITY nbsp "&#160;"> <!ENTITY nbsp "&#160;">
<!ENTITY pound "&#163;"> <!ENTITY pound "&#163;">
@ -35,7 +35,7 @@ that we can allow HTML block level entities within content elements -->
<!-- boolean means true or false --> <!-- boolean means true or false -->
<!ENTITY % Boolean "(true|false)" > <!ENTITY % Boolean "(true|false)" >
<!-- <!--
Locale is a string comprising an ISO 639 language code followed by a space Locale is a string comprising an ISO 639 language code followed by a space
followed by an ISO 3166 country code, or else the string 'default'. See: followed by an ISO 3166 country code, or else the string 'default'. See:
<URL:http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt> <URL:http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt>
@ -43,12 +43,12 @@ that we can allow HTML block level entities within content elements -->
--> -->
<!ENTITY % Locale "CDATA" > <!ENTITY % Locale "CDATA" >
<!-- <!--
permissions a group may have on an entity, list, page, form or field permissions a group may have on an entity, list, page, form or field
permissions are deemed to increase as you go right. A group cannot permissions are deemed to increase as you go right. A group cannot
have greater permission on a field than on the form it is in, or have greater permission on a field than on the form it is in, or
greater permission on form than the entity it belongs to greater permission on form than the entity it belongs to
none: none none: none
read: select read: select
insert: insert insert: insert
@ -61,10 +61,10 @@ that we can allow HTML block level entities within content elements -->
<!-- <!--
actions which should be cascaded to dependent objects. All these values except actions which should be cascaded to dependent objects. All these values except
'manual' are taken from Hibernate and should be passed through the adl2hibernate 'manual' are taken from Hibernate and should be passed through the adl2hibernate
mapping transparently. Relevent only for properties with type='entity', type='link' mapping transparently. Relevent only for properties with type='entity', type='link'
and type='list' and type='list'
all : cascade delete, save and update all : cascade delete, save and update
all-delete-orphan : see hibernate documentation; relates to transient objects only all-delete-orphan : see hibernate documentation; relates to transient objects only
delete : cascade delete actions, but not save and update delete : cascade delete actions, but not save and update
@ -74,10 +74,10 @@ that we can allow HTML block level entities within content elements -->
--> -->
<!ENTITY % CascadeActions "all|all-delete-orphan|delete|manual|save-update"> <!ENTITY % CascadeActions "all|all-delete-orphan|delete|manual|save-update">
<!-- <!--
data types which can be used in a typedef to provide validation - data types which can be used in a typedef to provide validation -
e.g. a string can be used with a regexp or a scalar can be used with e.g. a string can be used with a regexp or a scalar can be used with
min and max values min and max values
string: varchar java.sql.Types.VARCHAR string: varchar java.sql.Types.VARCHAR
integer: int java.sql.Types.INTEGER integer: int java.sql.Types.INTEGER
real: double java.sql.Types.DOUBLE real: double java.sql.Types.DOUBLE
@ -85,26 +85,26 @@ that we can allow HTML block level entities within content elements -->
date: date java.sql.Types.DATE date: date java.sql.Types.DATE
time: time java.sql.Types.TIME time: time java.sql.Types.TIME
timestamp: timestamp java.sql.Types.TIMESTAMP timestamp: timestamp java.sql.Types.TIMESTAMP
uploadable: varchar java.sql.Types.VARCHAR uploadable: varchar java.sql.Types.VARCHAR
image: varchar java.sql.Types.VARCHAR image: varchar java.sql.Types.VARCHAR
uploadable is as string but points to an uploaded file; image is as uploadable is as string but points to an uploaded file; image is as
uploadable but points to an uploadable graphical image file uploadable but points to an uploadable graphical image file
--> -->
<!ENTITY % DefinableDataTypes "string|integer|real|money|date|time|timestamp|uploadable" > <!ENTITY % DefinableDataTypes "string|integer|real|money|date|time|timestamp|uploadable" >
<!-- <!--
data types which are fairly straightforward translations of JDBC data types data types which are fairly straightforward translations of JDBC data types
boolean: boolean or java.sql.Types.BIT boolean: boolean or java.sql.Types.BIT
char(1) java.sql.Types.CHAR char(1) java.sql.Types.CHAR
text: text or java.sql.Types.LONGVARCHAR text: text or java.sql.Types.LONGVARCHAR
memo java.sql.Types.CLOB memo java.sql.Types.CLOB
--> -->
<!ENTITY % SimpleDataTypes "%DefinableDataTypes;|boolean|text" > <!ENTITY % SimpleDataTypes "%DefinableDataTypes;|boolean|text" >
<!-- <!--
data types which are more complex than SimpleDataTypes... data types which are more complex than SimpleDataTypes...
entity : a foreign key link to another entity (i.e. the 'many' end of a entity : a foreign key link to another entity (i.e. the 'many' end of a
one-to-many link); one-to-many link);
list : a list of some other entity that links to me (i.e. the 'one' end of list : a list of some other entity that links to me (i.e. the 'one' end of
a one-to-many link); a one-to-many link);
@ -114,7 +114,7 @@ that we can allow HTML block level entities within content elements -->
<!ENTITY % ComplexDataTypes "entity|link|list|defined" > <!ENTITY % ComplexDataTypes "entity|link|list|defined" >
<!-- <!--
data types which require special handling - which don't simply map onto data types which require special handling - which don't simply map onto
common SQL data types common SQL data types
geopos : a latitude/longitude pair (experimental and not yet implemented) geopos : a latitude/longitude pair (experimental and not yet implemented)
image : a raster image file, in jpeg|gif|png format (experimental, not yet implemented) image : a raster image file, in jpeg|gif|png format (experimental, not yet implemented)
@ -142,17 +142,17 @@ that we can allow HTML block level entities within content elements -->
present) present)
user-distinct: all properties which are user-distinct (NOTE: Not yet implemented) user-distinct: all properties which are user-distinct (NOTE: Not yet implemented)
listed: only those properties for which fields are explicitly listed listed: only those properties for which fields are explicitly listed
--> -->
<!ENTITY % PageAttrs <!ENTITY % PageAttrs
"name CDATA #REQUIRED "name CDATA #REQUIRED
properties (all|user-distinct|listed) #REQUIRED" > properties (all|user-distinct|listed) #REQUIRED" >
<!-- Actions for generators (mainly for keyfields - see entity 'generator', below <!-- Actions for generators (mainly for keyfields - see entity 'generator', below
assigned: In manually-maintained code, you contract to assign a value assigned: In manually-maintained code, you contract to assign a value
to this property before it is persisted. to this property before it is persisted.
guid: The system will supply a unique GUid value to this field guid: The system will supply a unique GUid value to this field
before it is persisted. before it is persisted.
mannual: You contract to supply a generatos class in manually maintained mannual: You contract to supply a generator class in manually maintained
code. code.
native: The database will supply a unique value to this field when it native: The database will supply a unique value to this field when it
is persisted; the value will be an integer. RECOMMENDED! is persisted; the value will be an integer. RECOMMENDED!
@ -163,7 +163,7 @@ that we can allow HTML block level entities within content elements -->
canonical: Whatever the normal canonical ordering for this datatype is - canonical: Whatever the normal canonical ordering for this datatype is -
typically alpha-numeric, except for dates, etc. typically alpha-numeric, except for dates, etc.
reverse-canonical: The reverse of the above reverse-canonical: The reverse of the above
possibly there should be some further values but I have no idea what these are possibly there should be some further values but I have no idea what these are
--> -->
<!ENTITY % Sequences "canonical|reverse-canonical"> <!ENTITY % Sequences "canonical|reverse-canonical">
@ -172,9 +172,9 @@ that we can allow HTML block level entities within content elements -->
<!-- Elements --> <!-- Elements -->
<!-- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --> <!-- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
<!-- <!--
the application that the document describes: required top level element the application that the document describes: required top level element
name: the name of this application name: the name of this application
version: the version number of this application version: the version number of this application
revision: the revision of the ADL document revision: the revision of the ADL document
@ -182,14 +182,14 @@ that we can allow HTML block level entities within content elements -->
xmlns: XML namespace, in case required xmlns: XML namespace, in case required
--> -->
<!ELEMENT application ( specification*, documentation?, content?, typedef*, group*, entity*)> <!ELEMENT application ( specification*, documentation?, content?, typedef*, group*, entity*)>
<!ATTLIST application <!ATTLIST application
name CDATA #REQUIRED name CDATA #REQUIRED
version CDATA #IMPLIED version CDATA #IMPLIED
revision CDATA #IMPLIED revision CDATA #IMPLIED
currency CDATA #IMPLIED currency CDATA #IMPLIED
xmlns CDATA #IMPLIED> xmlns CDATA #IMPLIED>
<!-- <!--
the definition of a defined type. At this stage a defined type is either the definition of a defined type. At this stage a defined type is either
a string in which case it must have size and pattern, or a string in which case it must have size and pattern, or
a scalar in which case it must have minimum and/or maximum a scalar in which case it must have minimum and/or maximum
@ -208,7 +208,7 @@ that we can allow HTML block level entities within content elements -->
--> -->
<!ELEMENT typedef (documentation?, in-implementation*, help*) > <!ELEMENT typedef (documentation?, in-implementation*, help*) >
<!ATTLIST typedef <!ATTLIST typedef
name CDATA #REQUIRED name CDATA #REQUIRED
type (%DefinableDataTypes;) #IMPLIED type (%DefinableDataTypes;) #IMPLIED
size CDATA #IMPLIED size CDATA #IMPLIED
@ -218,12 +218,12 @@ that we can allow HTML block level entities within content elements -->
<!-- <!--
information about how to translate a type into types known to different target information about how to translate a type into types known to different target
languages. TODO: Once again I'm not wholly comfortable with the name; I'm not languages. TODO: Once again I'm not wholly comfortable with the name; I'm not
really comfortable that this belongs in ADL at all. really comfortable that this belongs in ADL at all.
target: the target language target: the target language
value: the type to use in that target language value: the type to use in that target language
kind: OK, I confess I don't understand this, but Andrew needs it... kind: OK, I confess I don't understand this, but Andrew needs it...
--> -->
<!ELEMENT in-implementation (documentation?)> <!ELEMENT in-implementation (documentation?)>
@ -232,28 +232,28 @@ that we can allow HTML block level entities within content elements -->
value CDATA #REQUIRED value CDATA #REQUIRED
kind CDATA #IMPLIED> kind CDATA #IMPLIED>
<!-- <!--
a group of people with similar permissions to one another a group of people with similar permissions to one another
name: the name of this group name: the name of this group
parent: the name of a group of which this group is subset parent: the name of a group of which this group is subset
--> -->
<!ELEMENT group (documentation?)> <!ELEMENT group (documentation?)>
<!ATTLIST group <!ATTLIST group
name CDATA #REQUIRED name CDATA #REQUIRED
parent CDATA #IMPLIED> parent CDATA #IMPLIED>
<!-- <!--
an entity which has properties and relationships; maps onto a database an entity which has properties and relationships; maps onto a database
table or a Java serialisable class - or, of course, various other things table or a Java serialisable class - or, of course, various other things
name: obviously, the name of this entity name: obviously, the name of this entity
natural-key: if present, the name of a property of this entity which forms natural-key: if present, the name of a property of this entity which forms
a natural primary key [NOTE: Only partly implemented. NOTE: much of a natural primary key [NOTE: Only partly implemented. NOTE: much of
the present implementation assumes all primary keys will be the present implementation assumes all primary keys will be
integers. This needs to be fixed!] DEPRECATED: remove; replace with the integers. This needs to be fixed!] DEPRECATED: remove; replace with the
'key' element, below. 'key' element, below.
table: the name of the table in which this entity is stored. Defaults to same table: the name of the table in which this entity is stored. Defaults to same
@ -264,13 +264,13 @@ that we can allow HTML block level entities within content elements -->
--> -->
<!ELEMENT entity ( documentation?, prompt*, content?, key?, <!ELEMENT entity ( documentation?, prompt*, content?, key?,
property*, permission*, (form | page | list)*)> property*, permission*, (form | page | list)*)>
<!ATTLIST entity <!ATTLIST entity
name CDATA #REQUIRED name CDATA #REQUIRED
natural-key CDATA #IMPLIED natural-key CDATA #IMPLIED
table CDATA #IMPLIED table CDATA #IMPLIED
foreign %Boolean; #IMPLIED> foreign %Boolean; #IMPLIED>
<!-- <!--
contains documentation on the element which immediately contains it. TODO: contains documentation on the element which immediately contains it. TODO:
should HTML markup within a documentation element be allowed? If so, are should HTML markup within a documentation element be allowed? If so, are
there restrictions? there restrictions?
@ -284,33 +284,33 @@ that we can allow HTML block level entities within content elements -->
<!ELEMENT key (property*)> <!ELEMENT key (property*)>
<!-- <!--
a property (field) of an entity (table) a property (field) of an entity (table)
name: the name of this property. name: the name of this property.
type: the type of this property. type: the type of this property.
default: the default value of this property. There will probably be default: the default value of this property. There will probably be
magic values of this! magic values of this!
typedef: name of the typedef to use, it type = 'defined'. typedef: name of the typedef to use, it type = 'defined'.
distinct: distinct='system' required that every value in the system distinct: distinct='system' required that every value in the system
will be distinct (i.e. natural primary key); will be distinct (i.e. natural primary key);
distinct='user' implies that the value may be used by users distinct='user' implies that the value may be used by users
in distinguishing entities even if values are not formally in distinguishing entities even if values are not formally
unique; unique;
distinct='all' implies that the values are formally unique distinct='all' implies that the values are formally unique
/and/ are user friendly (NOTE: not implemented). /and/ are user friendly (NOTE: not implemented).
entity: if type='entity', the name of the entity this property is entity: if type='entity', the name of the entity this property is
a foreign key link to. a foreign key link to.
if type='list', the name of the entity that has a foreign if type='list', the name of the entity that has a foreign
key link to this entity key link to this entity
farkey: if type='list', the name of farside key in the listed farkey: if type='list', the name of farside key in the listed
entity; if type='entity' and the farside field to join to entity; if type='entity' and the farside field to join to
is not the farside primary key, then the name of that is not the farside primary key, then the name of that
farside field farside field
required: whether this propery is required (i.e. 'not null'). required: whether this propery is required (i.e. 'not null').
immutable: if true, once a value has been set it cannot be changed. immutable: if true, once a value has been set it cannot be changed.
size: fieldwidth of the property if specified. size: fieldwidth of the property if specified.
concrete: if set to 'false', this property is not stored in the concrete: if set to 'false', this property is not stored in the
database but must be computed (manually written code must database but must be computed (manually written code must
be provided to support this) be provided to support this)
cascade: what action(s) on the parent entity should be cascaded to cascade: what action(s) on the parent entity should be cascaded to
@ -319,13 +319,13 @@ that we can allow HTML block level entities within content elements -->
column: name of the column in a SQL database table in which this property column: name of the column in a SQL database table in which this property
is stored. TODO: Think about this. is stored. TODO: Think about this.
unsaved-value: unsaved-value:
of a property whose persistent value is set on first being of a property whose persistent value is set on first being
committed to persistent store, the value which it holds before committed to persistent store, the value which it holds before
it has been committed it has been committed
--> -->
<!ELEMENT property ( documentation?, generator?, (permission|option|prompt|help|ifmissing)*)> <!ELEMENT property ( documentation?, generator?, (permission|option|prompt|help|ifmissing)*)>
<!ATTLIST property <!ATTLIST property
name CDATA #REQUIRED name CDATA #REQUIRED
type (%AllDataTypes;) #REQUIRED type (%AllDataTypes;) #REQUIRED
default CDATA #IMPLIED default CDATA #IMPLIED
@ -334,24 +334,24 @@ that we can allow HTML block level entities within content elements -->
entity CDATA #IMPLIED entity CDATA #IMPLIED
farkey CDATA #IMPLIED farkey CDATA #IMPLIED
required %Boolean; #IMPLIED required %Boolean; #IMPLIED
immutable %Boolean; #IMPLIED immutable %Boolean; #IMPLIED
size CDATA #IMPLIED size CDATA #IMPLIED
column CDATA #IMPLIED column CDATA #IMPLIED
concrete %Boolean; #IMPLIED concrete %Boolean; #IMPLIED
cascade (%CascadeActions;) #IMPLIED> cascade (%CascadeActions;) #IMPLIED>
<!-- <!--
marks a property which is auto-generated by some part of the system. marks a property which is auto-generated by some part of the system.
This is based on the Hibernate construct, except that the Hibernate This is based on the Hibernate construct, except that the Hibernate
implementation folds both its internal generators and custom generators implementation folds both its internal generators and custom generators
onto the same attribute. This separates them onto two attributes so we onto the same attribute. This separates them onto two attributes so we
can police values for Hibernate's 'builtin' generators. can police values for Hibernate's 'builtin' generators.
action: one of the supported Hibernate builtin generators, or action: one of the supported Hibernate builtin generators, or
'manual'. 'native' is strongly recommended in most instances 'manual'. 'native' is strongly recommended in most instances
class: if action is 'manual', the name of a manually maintained class: if action is 'manual', the name of a manually maintained
class conforming to the Hibernate IdentifierGenerator class conforming to the Hibernate IdentifierGenerator
interface, or its equivalent in other languages interface, or its equivalent in other languages
--> -->
<!ELEMENT generator (documentation?, param*)> <!ELEMENT generator (documentation?, param*)>
@ -360,15 +360,15 @@ that we can allow HTML block level entities within content elements -->
class CDATA #IMPLIED> class CDATA #IMPLIED>
<!-- <!--
A parameter passed to the generator. Again, based on the Hibernate A parameter passed to the generator. Again, based on the Hibernate
implementation. TODO: #PCDATA is wrong as the content model, as embedded implementation. TODO: #PCDATA is wrong as the content model, as embedded
markup is definitely not allowed! markup is definitely not allowed!
name: the name of this parameter name: the name of this parameter
TODO: This needs to be renamed or removed because it conflicts with the TODO: This needs to be renamed or removed because it conflicts with the
XHTML element of the same name. In fact it could be simply removed since XHTML element of the same name. In fact it could be simply removed since
our usage is compatible with the XHTML usage, but it might be less our usage is compatible with the XHTML usage, but it might be less
ambiguous to rename it. ambiguous to rename it.
--> -->
<!ELEMENT param (#PCDATA)> <!ELEMENT param (#PCDATA)>
@ -377,40 +377,40 @@ that we can allow HTML block level entities within content elements -->
<!-- <!--
one of an explicit list of optional values a property may have one of an explicit list of optional values a property may have
NOTE: whether options get encoded at application layer or at database layer NOTE: whether options get encoded at application layer or at database layer
is UNDEFINED; either behaviour is correct. If at database layer it's also is UNDEFINED; either behaviour is correct. If at database layer it's also
UNDEFINED whether they're encoded as a single reference data table or as UNDEFINED whether they're encoded as a single reference data table or as
separate reference data tables for each property. separate reference data tables for each property.
value: the value of this option value: the value of this option
TODO: This needs to be renamed or removed because it conflicts with the TODO: This needs to be renamed or removed because it conflicts with the
XHTML element of the same name. In fact it could be simply removed since XHTML element of the same name. In fact it could be simply removed since
our usage is compatible with the XHTML usage, but it might be less our usage is compatible with the XHTML usage, but it might be less
ambiguous to rename it. ambiguous to rename it.
--> -->
<!ELEMENT option (documentation?, prompt*)> <!ELEMENT option (documentation?, prompt*)>
<!-- if the value is different from the prompt the user sees, specify it --> <!-- if the value is different from the prompt the user sees, specify it -->
<!ATTLIST option <!ATTLIST option
value CDATA #IMPLIED> value CDATA #IMPLIED>
<!-- <!--
permissions policy on an entity, a page, form, list or field permissions policy on an entity, a page, form, list or field
group: the group to which permission is granted group: the group to which permission is granted
permission: the permission which is granted to that group permission: the permission which is granted to that group
--> -->
<!ELEMENT permission (documentation?)> <!ELEMENT permission (documentation?)>
<!ATTLIST permission <!ATTLIST permission
group CDATA #REQUIRED group CDATA #REQUIRED
permission (%Permissions;) #REQUIRED> permission (%Permissions;) #REQUIRED>
<!-- <!--
pragmatic advice to generators of lists and forms, in the form of pragmatic advice to generators of lists and forms, in the form of
name/value pairs which may contain anything. Over time some pragmas name/value pairs which may contain anything. Over time some pragmas
will become 'well known', but the whole point of having a pragma will become 'well known', but the whole point of having a pragma
architecture is that it is extensible. architecture is that it is extensible.
@ -420,27 +420,27 @@ that we can allow HTML block level entities within content elements -->
name CDATA #REQUIRED name CDATA #REQUIRED
value CDATA #REQUIRED> value CDATA #REQUIRED>
<!-- <!--
a prompt for a property or field; used as the prompt text for a widget a prompt for a property or field; used as the prompt text for a widget
which edits it. Typically there will be only one of these per property which edits it. Typically there will be only one of these per property
per locale; if there are more than one all those matching the locale may per locale; if there are more than one all those matching the locale may
be concatenated, or just one may be used. be concatenated, or just one may be used.
prompt: the prompt to use prompt: the prompt to use
locale: the locale in which to prefer this prompt locale: the locale in which to prefer this prompt
--> -->
<!ELEMENT prompt (documentation?)> <!ELEMENT prompt (documentation?)>
<!ATTLIST prompt <!ATTLIST prompt
prompt CDATA #REQUIRED prompt CDATA #REQUIRED
locale %Locale; #REQUIRED > locale %Locale; #REQUIRED >
<!-- <!--
helptext about a property of an entity, or a field of a page, form or helptext about a property of an entity, or a field of a page, form or
list, or a typedef. Typically there will be only one of these per property list, or a typedef. Typically there will be only one of these per property
per locale; if there are more than one all those matching the locale may per locale; if there are more than one all those matching the locale may
be concatenated, or just one may be used. be concatenated, or just one may be used.
locale: the locale in which to prefer this prompt locale: the locale in which to prefer this prompt
--> -->
<!ELEMENT help (#PCDATA)> <!ELEMENT help (#PCDATA)>
<!ATTLIST help <!ATTLIST help
@ -448,7 +448,7 @@ that we can allow HTML block level entities within content elements -->
xmlns CDATA #IMPLIED > xmlns CDATA #IMPLIED >
<!-- <!--
helpful text to be shown if a property value is missing, typically when helpful text to be shown if a property value is missing, typically when
a form is submitted. Typically there will be only one of these per property a form is submitted. Typically there will be only one of these per property
per locale; if there are more than one all those matching the locale may per locale; if there are more than one all those matching the locale may
be concatenated, or just one may be used. Later there may be more sophisticated be concatenated, or just one may be used. Later there may be more sophisticated
@ -462,7 +462,7 @@ that we can allow HTML block level entities within content elements -->
<!-- a form through which an entity may be added or edited <!-- a form through which an entity may be added or edited
TODO: This needs to be renamed because it conflicts with the TODO: This needs to be renamed because it conflicts with the
XHTML element of the same name. XHTML element of the same name.
--> -->
<!ELEMENT form (documentation?, ( %PageStuff;)*)> <!ELEMENT form (documentation?, ( %PageStuff;)*)>
<!ATTLIST form %PageAttrs;> <!ATTLIST form %PageAttrs;>
@ -472,7 +472,7 @@ that we can allow HTML block level entities within content elements -->
<!ELEMENT page (documentation?, ( %PageStuff;)*)> <!ELEMENT page (documentation?, ( %PageStuff;)*)>
<!ATTLIST page %PageAttrs;> <!ATTLIST page %PageAttrs;>
<!-- an ordering or records in a list <!-- an ordering or records in a list
property: the property on which to order property: the property on which to order
sequence: the sequence in which to order sequence: the sequence in which to order
--> -->
@ -481,11 +481,11 @@ that we can allow HTML block level entities within content elements -->
property CDATA #REQUIRED property CDATA #REQUIRED
sequence (%Sequences;) #IMPLIED> sequence (%Sequences;) #IMPLIED>
<!-- <!--
a list on which entities of a given type are listed a list on which entities of a given type are listed
onselect: name of form/page/list to go to when onselect: name of form/page/list to go to when
a selection is made from the list a selection is made from the list
--> -->
<!ELEMENT list (documentation?, ( %PageStuff;|order)*)> <!ELEMENT list (documentation?, ( %PageStuff;|order)*)>
<!ATTLIST list %PageAttrs; <!ATTLIST list %PageAttrs;
@ -495,7 +495,7 @@ that we can allow HTML block level entities within content elements -->
<!-- <!--
a subsidiary list, on which entities related to primary a subsidiary list, on which entities related to primary
entities in the enclosing page or list are listed entities in the enclosing page or list are listed
property: the property of the enclosing entity that this property: the property of the enclosing entity that this
list displays (obviously, must be of type='list') list displays (obviously, must be of type='list')
onselect: the form or page of the listed entity to call onselect: the form or page of the listed entity to call
@ -510,7 +510,7 @@ that we can allow HTML block level entities within content elements -->
canadd %Boolean; #IMPLIED> canadd %Boolean; #IMPLIED>
<!-- <!--
a group of fields and other controls within a form or list, which the a group of fields and other controls within a form or list, which the
renderer might render as a single pane in a tabbed display, for example. renderer might render as a single pane in a tabbed display, for example.
--> -->
<!ELEMENT fieldgroup (documentation?, (prompt|permission|%FieldStuff;)*)> <!ELEMENT fieldgroup (documentation?, (prompt|permission|%FieldStuff;)*)>
@ -523,10 +523,10 @@ that we can allow HTML block level entities within content elements -->
property: the property which this field displays/edits property: the property which this field displays/edits
--> -->
<!ELEMENT field (documentation?, (prompt|help|permission)*) > <!ELEMENT field (documentation?, (prompt|help|permission)*) >
<!ATTLIST field <!ATTLIST field
property CDATA #REQUIRED > property CDATA #REQUIRED >
<!-- a verb is something that may be done through a form. Probably the verbs 'store' <!-- a verb is something that may be done through a form. Probably the verbs 'store'
and 'delete' are implied, but maybe they need to be explicitly declared. The 'verb' and 'delete' are implied, but maybe they need to be explicitly declared. The 'verb'
attribute of the verb is what gets returned to the controller --> attribute of the verb is what gets returned to the controller -->
<!ELEMENT verb (documentation?, (prompt|help|permission)*) > <!ELEMENT verb (documentation?, (prompt|help|permission)*) >
@ -539,14 +539,14 @@ that we can allow HTML block level entities within content elements -->
<!ELEMENT content (%Content;)*> <!ELEMENT content (%Content;)*>
<!-- <!--
content to place in the head of the generated document; this is #PCDATA content to place in the head of the generated document; this is #PCDATA
because it will almost certainly belong to a different namespace because it will almost certainly belong to a different namespace
(usually HTML) (usually HTML)
TODO: This needs to be renamed or removed because it conflicts with the TODO: This needs to be renamed or removed because it conflicts with the
XHTML element of the same name. In fact it could be simply removed since XHTML element of the same name. In fact it could be simply removed since
our usage is compatible with the XHTML usage, but it might be less our usage is compatible with the XHTML usage, but it might be less
ambiguous to rename it. ambiguous to rename it.
--> -->
<!ELEMENT head (#PCDATA) > <!ELEMENT head (#PCDATA) >
@ -554,30 +554,30 @@ that we can allow HTML block level entities within content elements -->
xmlns CDATA #IMPLIED> xmlns CDATA #IMPLIED>
<!-- <!--
content to place in the top of the body of the generated document; content to place in the top of the body of the generated document;
this is %Flow; which is any HTML block or inline level element. this is %Flow; which is any HTML block or inline level element.
--> -->
<!ELEMENT top (#PCDATA) > <!ELEMENT top (#PCDATA) >
<!ATTLIST top <!ATTLIST top
xmlns CDATA #IMPLIED> xmlns CDATA #IMPLIED>
<!-- <!--
content to place at the foot of the body of the generated document; content to place at the foot of the body of the generated document;
this is %Flow; which is any HTML block or inline level element. this is %Flow; which is any HTML block or inline level element.
--> -->
<!ELEMENT foot (#PCDATA) > <!ELEMENT foot (#PCDATA) >
<!ATTLIST foot <!ATTLIST foot
xmlns CDATA #IMPLIED> xmlns CDATA #IMPLIED>
<!-- <!--
The 'specification' and 'reference' elements are for documentation only, The 'specification' and 'reference' elements are for documentation only,
and do not contribute to the engineering of the application described. and do not contribute to the engineering of the application described.
A specification element is intended chiefly to declare the reference A specification element is intended chiefly to declare the reference
documents which may be used in documentation elements later in the documents which may be used in documentation elements later in the
document. document.
url: The URL from which the document referenced can be retrieved url: The URL from which the document referenced can be retrieved
name: The full name (title) given to this document name: The full name (title) given to this document
abbr: A convenient abbreviated name abbr: A convenient abbreviated name
@ -589,12 +589,12 @@ that we can allow HTML block level entities within content elements -->
abbr CDATA #REQUIRED abbr CDATA #REQUIRED
> >
<!-- <!--
The 'specification' and 'reference' elements are for documentation only, The 'specification' and 'reference' elements are for documentation only,
and do not contribute to the engineering of the application described. and do not contribute to the engineering of the application described.
A reference element is a reference to a specifying document. A reference element is a reference to a specifying document.
abbr: The abbreviated name of the specification to which this abbr: The abbreviated name of the specification to which this
reference refers reference refers
section: The 'anchor part' (part following a hash character) which, section: The 'anchor part' (part following a hash character) which,

View file

@ -6,7 +6,7 @@
[clojure.string :as s] [clojure.string :as s]
[clj-time.core :as t] [clj-time.core :as t]
[clj-time.format :as f] [clj-time.format :as f]
[adl.utils :refer [singularise is-link-table?]])) [adl.utils :refer :all]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; ;;;;
@ -32,32 +32,22 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn key-names [entity-map]
(let [k (first (filter #(= (:tag %) :key) (:content entity-map)))]
(remove
nil?
(map
#(:name (:attrs %))
(filter #(= (:tag %) :property) (:content k))))))
(defn has-primary-key? [entity-map]
(not (empty? (key-names entity-map))))
(defn has-non-key-properties? [entity-map]
(not
(empty? (filter #(= (:tag %) :property) (:content entity-map)))))
(defn where-clause [entity-map] (defn where-clause [entity-map]
(let (let
[entity-name (:name (:attrs entity-map))] [entity-name (:name (:attrs entity-map))]
(str (str
"WHERE " entity-name "." "WHERE " entity-name "."
(s/join (s/join
(str " AND\n\t" entity-name ".") (str " AND\n\t" entity-name ".")
(map #(str % " = " (keyword %)) (key-names entity-map)))))) (map
#(let [target (keyword (-> % :attrs :name))]
(str
(name target) " = "
(if
(quoted-type? %)
(str "'" target "'")
target)))
(key-properties entity-map))))))
(defn order-by-clause [entity-map] (defn order-by-clause [entity-map]
@ -66,69 +56,77 @@
preferred (map preferred (map
#(:name (:attrs %)) #(:name (:attrs %))
(filter #(and (filter #(and
(= (-> % :attrs :distinct) "user") (#{"all", "user"} (-> % :attrs :distinct))
(= (-> % :tag) :property)) (= (-> % :tag) :property))
(-> entity-map :content)))] (concat (properties entity-map)(key-properties entity-map))))]
(str (str
"ORDER BY " entity-name "." "ORDER BY " entity-name "."
(s/join (s/join
(str ",\n\t" entity-name ".") (str ",\n\t" entity-name ".")
(doall (flatten (cons preferred (key-names entity-map)))))))) (doall (flatten (cons preferred (filter
#(not (#{"all", "user"} %))
(key-names entity-map)))))))))
(defn property-names [entity-map]
(map #(:name (:attrs %)) (filter #(= (-> % :tag) :property) (:content entity-map))))
(defn insert-query [entity-map] (defn insert-query [entity-map]
(let [entity-name (:name (:attrs entity-map)) (let [entity-name (:name (:attrs entity-map))
pretty-name (singularise entity-name) pretty-name (singularise entity-name)
all-property-names (property-names entity-map) props (concat (properties entity-map) (insertable-key-properties entity-map))
pnames (map #(-> % :attrs :name) props)
query-name (str "create-" pretty-name "!") query-name (str "create-" pretty-name "!")
signature ":! :n"] signature ":! :n"]
(hash-map (hash-map
(keyword query-name) (keyword query-name)
{:name query-name {:name query-name
:signature signature :signature signature
:entity entity-map :entity entity-map
:type :insert-1 :type :insert-1
:query :query
(str "-- :name " query-name " " signature "\n" (str "-- :name " query-name " " signature "\n"
"-- :doc creates a new " pretty-name " record\n" "-- :doc creates a new " pretty-name " record\n"
"INSERT INTO " entity-name " (" "INSERT INTO " entity-name " ("
(s/join ",\n\t" all-property-names) (s/join ",\n\t" pnames)
")\nVALUES (" ")\nVALUES ("
(s/join ",\n\t" (map keyword all-property-names)) (s/join ",\n\t"
")" (map
(if #(let [target (keyword (-> % :attrs :name))]
(has-primary-key? entity-map) (if
(str "\nreturning " (s/join ",\n\t" (key-names entity-map)))) (quoted-type? %)
"\n\n")}))) (str "'" target "'")
target))
props))
")"
(if
(has-primary-key? entity-map)
(str "\nreturning " (s/join ",\n\t" (key-names entity-map))))
"\n\n")})))
(defn update-query [entity-map] (defn update-query [entity-map]
(if (if
(and (and
(has-primary-key? entity-map) (has-primary-key? entity-map)
(has-non-key-properties? entity-map)) (has-non-key-properties? entity-map))
(let [entity-name (:name (:attrs entity-map)) (let [entity-name (:name (:attrs entity-map))
pretty-name (singularise entity-name) pretty-name (singularise entity-name)
property-names (property-names entity-map) property-names (property-names entity-map)
query-name (str "update-" pretty-name "!") query-name (str "update-" pretty-name "!")
signature ":! :n"] signature ":! :n"]
(hash-map (hash-map
(keyword query-name) (keyword query-name)
{:name query-name {:name query-name
:signature signature :signature signature
:entity entity-map :entity entity-map
:type :update-1 :type :update-1
:query :query
(str "-- :name " query-name " " signature "\n" (str "-- :name " query-name " " signature "\n"
"-- :doc updates an existing " pretty-name " record\n" "-- :doc updates an existing " pretty-name " record\n"
"UPDATE " entity-name "\n" "UPDATE " entity-name "\n"
"SET " "SET "
(s/join ",\n\t" (map #(str % " = " (keyword %)) property-names)) (s/join ",\n\t" (map #(str % " = " (keyword %)) property-names))
"\n" "\n"
(where-clause entity-map) (where-clause entity-map)
"\n\n")})) "\n\n")}))
{})) {}))
@ -137,11 +135,11 @@
pretty-name (singularise entity-name) pretty-name (singularise entity-name)
query-name (str "search-strings-" pretty-name) query-name (str "search-strings-" pretty-name)
signature ":? :1" signature ":? :1"
props (concat (properties entity-map) (insertable-key-properties entity-map))
string-fields (filter string-fields (filter
#(and ;; TODO: should also allow typdefed fields which typedef to string.
(= (-> % :attrs :type) "string") #(= (-> % :attrs :type) "string")
(= (:tag %) :property)) props)]
(-> entity-map :content))]
(if (if
(empty? string-fields) (empty? string-fields)
{} {}
@ -164,8 +162,8 @@
"\n" "\n"
(order-by-clause entity-map) (order-by-clause entity-map)
"\n" "\n"
"--~ (if (:offset params) \"OFFSET :offset \") \n" "--~ (if (:offset params) \"OFFSET :offset \") \n"
"--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")" "--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")"
"\n\n")})))) "\n\n")}))))
@ -177,17 +175,17 @@
query-name (str "get-" pretty-name) query-name (str "get-" pretty-name)
signature ":? :1"] signature ":? :1"]
(hash-map (hash-map
(keyword query-name) (keyword query-name)
{:name query-name {:name query-name
:signature signature :signature signature
:entity entity-map :entity entity-map
:type :select-1 :type :select-1
:query :query
(str "-- :name " query-name " " signature "\n" (str "-- :name " query-name " " signature "\n"
"-- :doc selects an existing " pretty-name " record\n" "-- :doc selects an existing " pretty-name " record\n"
"SELECT * FROM " entity-name "\n" "SELECT * FROM " entity-name "\n"
(where-clause entity-map) (where-clause entity-map)
"\n\n")})) "\n\n")}))
{})) {}))
@ -201,19 +199,19 @@
query-name (str "list-" entity-name) query-name (str "list-" entity-name)
signature ":? :*"] signature ":? :*"]
(hash-map (hash-map
(keyword query-name) (keyword query-name)
{:name query-name {:name query-name
:signature signature :signature signature
:entity entity-map :entity entity-map
:type :select-many :type :select-many
:query :query
(str "-- :name " query-name " " signature "\n" (str "-- :name " query-name " " signature "\n"
"-- :doc lists all existing " pretty-name " records\n" "-- :doc lists all existing " pretty-name " records\n"
"SELECT * FROM " entity-name "\n" "SELECT * FROM " entity-name "\n"
(order-by-clause entity-map) "\n" (order-by-clause entity-map) "\n"
"--~ (if (:offset params) \"OFFSET :offset \") \n" "--~ (if (:offset params) \"OFFSET :offset \") \n"
"--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")" "--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")"
"\n\n")}))) "\n\n")})))
(defn foreign-queries [entity-map entities-map] (defn foreign-queries [entity-map entities-map]
@ -221,39 +219,39 @@
pretty-name (singularise entity-name) pretty-name (singularise entity-name)
links (filter #(-> % :attrs :entity) (-> entity-map :content :properties vals))] links (filter #(-> % :attrs :entity) (-> entity-map :content :properties vals))]
(apply (apply
merge merge
(map (map
#(let [far-name (-> % :attrs :entity) #(let [far-name (-> % :attrs :entity)
far-entity ((keyword far-name) entities-map) far-entity ((keyword far-name) entities-map)
pretty-far (s/replace (s/replace far-name #"_" "-") #"s$" "") pretty-far (s/replace (s/replace far-name #"_" "-") #"s$" "")
farkey (-> % :attrs :farkey) farkey (-> % :attrs :farkey)
link-field (-> % :attrs :name) link-field (-> % :attrs :name)
query-name (str "list-" entity-name "-by-" pretty-far) query-name (str "list-" entity-name "-by-" pretty-far)
signature ":? :*"] signature ":? :*"]
(hash-map (hash-map
(keyword query-name) (keyword query-name)
{:name query-name {:name query-name
:signature signature :signature signature
:entity entity-map :entity entity-map
:type :select-one-to-many :type :select-one-to-many
:far-entity far-entity :far-entity far-entity
:query :query
(str "-- :name " query-name " " signature "\n" (str "-- :name " query-name " " signature "\n"
"-- :doc lists all existing " pretty-name " records related to a given " pretty-far "\n" "-- :doc lists all existing " pretty-name " records related to a given " pretty-far "\n"
"SELECT * \nFROM " entity-name "\n" "SELECT * \nFROM " entity-name "\n"
"WHERE " entity-name "." link-field " = :id\n" "WHERE " entity-name "." link-field " = :id\n"
(order-by-clause entity-map) (order-by-clause entity-map)
"\n\n")})) "\n\n")}))
links)))) links))))
(defn link-table-query [near link far] (defn link-table-query [near link far]
(let [properties (-> link :content :properties vals) (let [properties (-> link :content :properties vals)
links (apply links (apply
merge merge
(map (map
#(hash-map (keyword (-> % :attrs :entity)) %) #(hash-map (keyword (-> % :attrs :entity)) %)
(filter #(-> % :attrs :entity) properties))) (filter #(-> % :attrs :entity) properties)))
near-name (-> near :attrs :name) near-name (-> near :attrs :name)
link-name (-> link :attrs :name) link-name (-> link :attrs :name)
far-name (-> far :attrs :name) far-name (-> far :attrs :name)
@ -261,37 +259,37 @@
query-name (str "list-" link-name "-" near-name "-by-" pretty-far) query-name (str "list-" link-name "-" near-name "-by-" pretty-far)
signature ":? :*"] signature ":? :*"]
(hash-map (hash-map
(keyword query-name) (keyword query-name)
{:name query-name {:name query-name
:signature signature :signature signature
:entity link :entity link
:type :select-many-to-many :type :select-many-to-many
:near-entity near :near-entity near
:far-entity far :far-entity far
:query :query
(str "-- :name " query-name " " signature " \n" (str "-- :name " query-name " " signature " \n"
"-- :doc lists all existing " near-name " records related through " link-name " to a given " pretty-far "\n" "-- :doc lists all existing " near-name " records related through " link-name " to a given " pretty-far "\n"
"SELECT "near-name ".*\n" "SELECT "near-name ".*\n"
"FROM " near-name ", " link-name "\n" "FROM " near-name ", " link-name "\n"
"WHERE " near-name "." (first (key-names near)) " = " link-name "." (-> (links (keyword near-name)) :attrs :name) "\n\t" "WHERE " near-name "." (first (key-names near)) " = " link-name "." (-> (links (keyword near-name)) :attrs :name) "\n\t"
"AND " link-name "." (-> (links (keyword far-name)) :attrs :name) " = :id\n" "AND " link-name "." (-> (links (keyword far-name)) :attrs :name) " = :id\n"
(order-by-clause near) (order-by-clause near)
"\n\n")}))) "\n\n")})))
(defn link-table-queries [entity-map entities-map] (defn link-table-queries [entity-map entities-map]
(let (let
[entities (map [entities (map
#((keyword %) entities-map) #((keyword %) entities-map)
(remove nil? (map #(-> % :attrs :entity) (-> entity-map :content :properties vals)))) (remove nil? (map #(-> % :attrs :entity) (-> entity-map :content :properties vals))))
pairs (combinations entities 2)] pairs (combinations entities 2)]
(apply (apply
merge merge
(map (map
#(merge #(merge
(link-table-query (nth % 0) entity-map (nth % 1)) (link-table-query (nth % 0) entity-map (nth % 1))
(link-table-query (nth % 1) entity-map (nth % 0))) (link-table-query (nth % 1) entity-map (nth % 0)))
pairs)))) pairs))))
@ -303,34 +301,34 @@
query-name (str "delete-" pretty-name "!") query-name (str "delete-" pretty-name "!")
signature ":! :n"] signature ":! :n"]
(hash-map (hash-map
(keyword query-name) (keyword query-name)
{:name query-name {:name query-name
:signature signature :signature signature
:entity entity-map :entity entity-map
:type :delete-1 :type :delete-1
:query :query
(str "-- :name " query-name " " signature "\n" (str "-- :name " query-name " " signature "\n"
"-- :doc updates an existing " pretty-name " record\n" "-- :doc updates an existing " pretty-name " record\n"
"DELETE FROM " entity-name "\n" "DELETE FROM " entity-name "\n"
(where-clause entity-map) (where-clause entity-map)
"\n\n")})))) "\n\n")}))))
(defn queries (defn queries
[entity-map entities-map] [entity-map entities-map]
(merge (merge
{} {}
(insert-query entity-map) (insert-query entity-map)
(update-query entity-map) (update-query entity-map)
(delete-query entity-map) (delete-query entity-map)
(if (if
(is-link-table? entity-map) (is-link-table? entity-map)
(link-table-queries entity-map entities-map) (link-table-queries entity-map entities-map)
(merge (merge
(select-query entity-map) (select-query entity-map)
(list-query entity-map) (list-query entity-map)
(search-query entity-map) (search-query entity-map)
(foreign-queries entity-map entities-map))))) (foreign-queries entity-map entities-map)))))
;; (defn migrations-to-queries-sql ;; (defn migrations-to-queries-sql

View file

@ -1,11 +1,126 @@
(ns adl.utils (ns adl.utils
(:require [clojure.string :as s])) (:require [clojure.string :as s]))
(defn singularise [string] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; adl.utils: utility functions generally useful to generators.
;;;;
;;;; This program is free software; you can redistribute it and/or
;;;; modify it under the terms of the GNU General Public License
;;;; as published by the Free Software Foundation; either version 2
;;;; of the License, or (at your option) any later version.
;;;;
;;;; This program is distributed in the hope that it will be useful,
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;;; GNU General Public License for more details.
;;;;
;;;; You should have received a copy of the GNU General Public License
;;;; along with this program; if not, write to the Free Software
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
;;;; USA.
;;;;
;;;; Copyright (C) 2018 Simon Brooke
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; **Argument name conventions**: arguments with names of the form `*-map`
;;; represent elements extracted from an ADL XML file as parsed by
;;; `clojure.xml/parse`. Thus `entity-map` represents an ADL entity,
;;; `property-map` a property, and so on.
;;;
;;; Generally, `(:tag x-map) => "x"`, and for every such object
;;; `(:attrs x-map)` should return a map of attributes whose keys
;;; are keywords and whose values are strings.
(defn singularise
"Assuming this string represents an English language plural noun,
construct a Clojure symbol name which represents the singular."
[string]
(s/replace (s/replace (s/replace string #"_" "-") #"s$" "") #"ie$" "y")) (s/replace (s/replace (s/replace string #"_" "-") #"s$" "") #"ie$" "y"))
(defn entities
[application-map]
(filter #(= (-> % :tag) :entity) (:content application-map)))
(defn is-link-table? (defn is-link-table?
"Does this `entity-map` represent a pure link table?"
[entity-map] [entity-map]
(let [properties (-> entity-map :content :properties vals) (let [properties (-> entity-map :content :properties vals)
links (filter #(-> % :attrs :entity) properties)] links (filter #(-> % :attrs :entity) properties)]
(= (count properties) (count links)))) (= (count properties) (count links))))
(defn key-properties
"Return a list of all properties in the primary key of this `entity-map`."
[entity-map]
(filter
#(= (:tag %) :property)
(:content
;; there's required to be only one key element in and entity element
(first
(filter
#(= (:tag %) :key)
(:content entity-map))))))
(defn insertable-key-properties
"List properties in the key of the entity indicated by this `entity-map`
which should be inserted.
A key property is insertable it it is not `system` (database) generated.
But note that `system` is the default."
[entity-map]
(filter
#(let
[generator (-> % :attrs :generator)]
(not
(or (nil? generator)
(= generator "system"))))
(key-properties entity-map)))
(defn key-names
"List the names of all properties in the primary key of this `entity-map`."
[entity-map]
(remove
nil?
(map
#(:name (:attrs %))
(key-properties entity-map))))
(defn has-primary-key?
"True if this `entity-map` has a primary key."
[entity-map]
(not (empty? (key-names entity-map))))
(defn properties
"List the non-primary-key properties of this `entity-map`."
[entity-map]
(filter #(= (-> % :tag) :property) (:content entity-map)))
(defn has-non-key-properties?
"True if this `entity-map` has properties which do not form part of the
primary key."
[entity-map]
(not
(empty? (properties entity-map))))
(defn property-names
"List the names of non-primary-key properties of this `entity-map`."
[entity-map]
(map #(:name (:attrs %)) (properties entity-map)))
(defn quoted-type?
"Is the type of the property represented by this `property-map` one whose
values should be quoted in SQL queries?
TODO: this won't work for typedef types, which means we need to pass the
entire parsed ADL down the chain to here (and probably, generally) so that
we can resolve issues like that."
[property-map]
(#{"string", "text", "date", "time", "timestamp"} (-> property-map :attrs :type)))

View file

@ -1,6 +1,22 @@
(ns adl.to-hugsql-queries-test (ns adl.to-hugsql-queries-test
(:require [clojure.test :refer :all] (:require [clojure.string :as s]
[adl.to-hugsql-queries :refer :all])) [clojure.test :refer :all]
[adl.to-hugsql-queries :refer :all]
[adl.utils :refer :all]))
(defn string-equal-ignore-whitespace
"I don't want unit tests to fail just because emitted whitespace changes."
[a b]
(if
(and
(string? a)
(string? b))
(let
[pattern #"[\s]+"
aa (s/replace a pattern " ")
bb (s/replace b pattern " ")]
(= aa bb))
(= a b)))
(deftest entity-tests (deftest entity-tests
(let [xml {:tag :entity, (let [xml {:tag :entity,
@ -32,67 +48,120 @@
(testing "user distinct properties should provide the default ordering" (testing "user distinct properties should provide the default ordering"
(let [expected "ORDER BY address.street,\n\taddress.postcode,\n\taddress.id" (let [expected "ORDER BY address.street,\n\taddress.postcode,\n\taddress.id"
actual (order-by-clause xml)] actual (order-by-clause xml)]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "keys name extraction" (testing "keys name extraction"
(let [expected '("id") (let [expected '("id")
actual (key-names xml)] actual (key-names xml)]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "primary key test" (testing "primary key test"
(let [expected true (let [expected true
actual (has-primary-key? xml)] actual (has-primary-key? xml)]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "non-key properties test" (testing "non-key properties test"
(let [expected true (let [expected true
actual (has-non-key-properties? xml)] actual (has-non-key-properties? xml)]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "insert query generation" (testing "insert query generation"
(let [expected "-- :name create-addres! :! :n\n-- :doc creates a new addres record\nINSERT INTO address (street,\n\ttown,\n\tpostcode)\nVALUES (:street,\n\t:town,\n\t:postcode)\nreturning id\n\n" (let [expected "-- :name create-addres! :! :n\n-- :doc creates a new addres record\nINSERT INTO address (street,\n\ttown,\n\tpostcode)\nVALUES (':street',\n\t':town',\n\t':postcode')\nreturning id\n\n"
actual (:query (first (vals (insert-query xml))))] actual (:query (first (vals (insert-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "insert query signature" (testing "insert query signature"
(let [expected ":! :n" (let [expected ":! :n"
actual (:signature (first (vals (insert-query xml))))] actual (:signature (first (vals (insert-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "update query generation" (testing "update query generation"
(let [expected "-- :name update-addres! :! :n\n-- :doc updates an existing addres record\nUPDATE address\nSET street = :street,\n\ttown = :town,\n\tpostcode = :postcode\nWHERE address.id = :id\n\n" (let [expected "-- :name update-addres! :! :n\n-- :doc updates an existing addres record\nUPDATE address\nSET street = :street,\n\ttown = :town,\n\tpostcode = :postcode\nWHERE address.id = :id\n\n"
actual (:query (first (vals (update-query xml))))] actual (:query (first (vals (update-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "update query signature" (testing "update query signature"
(let [expected ":! :n" (let [expected ":! :n"
actual (:signature (first (vals (update-query xml))))] actual (:signature (first (vals (update-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "search query generation" (testing "search query generation"
(let [expected "-- :name search-strings-addres :? :1\n-- :doc selects existing address records having any string field matching `:pattern` by substring match\nSELECT * FROM address\nWHERE street LIKE '%:pattern%'\n\tOR town LIKE '%:pattern%'\n\tOR postcode LIKE '%:pattern%'\nORDER BY address.street,\n\taddress.postcode,\n\taddress.id\n--~ (if (:offset params) \"OFFSET :offset \") \n--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n" (let [expected "-- :name search-strings-addres :? :1\n-- :doc selects existing address records having any string field matching `:pattern` by substring match\nSELECT * FROM address\nWHERE street LIKE '%:pattern%'\n\tOR town LIKE '%:pattern%'\n\tOR postcode LIKE '%:pattern%'\nORDER BY address.street,\n\taddress.postcode,\n\taddress.id\n--~ (if (:offset params) \"OFFSET :offset \") \n--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n"
actual (:query (first (vals (search-query xml))))] actual (:query (first (vals (search-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "search query signature" (testing "search query signature"
(let [expected ":? :1" (let [expected ":? :1"
actual (:signature (first (vals (search-query xml))))] actual (:signature (first (vals (search-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "select query generation" (testing "select query generation"
(let [expected "-- :name get-addres :? :1\n-- :doc selects an existing addres record\nSELECT * FROM address\nWHERE address.id = :id\n\n" (let [expected "-- :name get-addres :? :1\n-- :doc selects an existing addres record\nSELECT * FROM address\nWHERE address.id = :id\n\n"
actual (:query (first (vals (select-query xml))))] actual (:query (first (vals (select-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "select query signature" (testing "select query signature"
(let [expected ":? :1" (let [expected ":? :1"
actual (:signature (first (vals (select-query xml))))] actual (:signature (first (vals (select-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "list query generation" (testing "list query generation"
(let [expected "-- :name list-address :? :*\n-- :doc lists all existing addres records\nSELECT * FROM address\nORDER BY address.street,\n\taddress.postcode,\n\taddress.id\n--~ (if (:offset params) \"OFFSET :offset \") \n--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n" (let [expected "-- :name list-address :? :*\n-- :doc lists all existing addres records\nSELECT * FROM address\nORDER BY address.street,\n\taddress.postcode,\n\taddress.id\n--~ (if (:offset params) \"OFFSET :offset \") \n--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n"
actual (:query (first (vals (list-query xml))))] actual (:query (first (vals (list-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "list query signature" (testing "list query signature"
(let [expected ":? :*" (let [expected ":? :*"
actual (:signature (first (vals (list-query xml))))] actual (:signature (first (vals (list-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "delete query generation" (testing "delete query generation"
(let [expected "-- :name delete-addres! :! :n\n-- :doc updates an existing addres record\nDELETE FROM address\nWHERE address.id = :id\n\n" (let [expected "-- :name delete-addres! :! :n\n-- :doc updates an existing addres record\nDELETE FROM address\nWHERE address.id = :id\n\n"
actual (:query (first (vals (delete-query xml))))] actual (:query (first (vals (delete-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
(testing "delete query signature" (testing "delete query signature"
(let [expected ":! :n" (let [expected ":! :n"
actual (:signature (first (vals (delete-query xml))))] actual (:signature (first (vals (delete-query xml))))]
(is (= actual expected)))) (is (string-equal-ignore-whitespace actual expected))))
)) ))
(deftest complex-key-tests
(let [xml {:tag :entity,
:attrs {:name "address"},
:content
[{:tag :key,
:attrs nil,
:content
[{:tag :property,
:attrs
{:immutable "true",
:required "true",
:distinct "system",
:type "integer",
:name "id"},
:content
[{:tag :generator, :attrs {:action "native"}, :content nil}]}
{:tag :property,
:attrs
{:immutable "true",
:required "true",
:distinct "all",
:generator "assigned"
:type "string",
:size "12"
:name "postcode"},
:content
[{:tag :generator, :attrs {:action "native"}, :content nil}]}
]}
{:tag :property,
:attrs
{:distinct "user", :size "128", :type "string", :name "street"},
:content nil}
{:tag :property,
:attrs {:size "64", :type "string", :name "town"},
:content nil}
]}]
(testing "insert query generation - compound key, non system generated field in key"
(let [expected "-- :name create-addres! :! :n\n-- :doc creates a new addres record\nINSERT INTO address (street,\n\ttown,\n\tpostcode)\nVALUES (':street',\n\t':town',\n\t':postcode')\nreturning id,\n\tpostcode\n\n"
actual (:query (first (vals (insert-query xml))))]
(is (string-equal-ignore-whitespace actual expected))))
(testing "update query generation - compound key"
(let [expected "-- :name update-addres! :! :n\n-- :doc updates an existing addres record\nUPDATE address\nSET street = :street,\n\ttown = :town\nWHERE address.id = :id AND\n\taddress.postcode = ':postcode'\n\n"
actual (:query (first (vals (update-query xml))))]
(is (string-equal-ignore-whitespace actual expected))))
(testing "search query generation - user-distinct field in key"
(let [expected "-- :name search-strings-addres :? :1\n-- :doc selects existing address records having any string field matching `:pattern` by substring match\nSELECT * FROM address\nWHERE street LIKE '%:pattern%'\n\tOR town LIKE '%:pattern%'\n\tOR postcode LIKE '%:pattern%'\nORDER BY address.street,\n\taddress.postcode,\n\taddress.id,\n\taddress.postcode\n--~ (if (:offset params) \"OFFSET :offset \") \n--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n"
actual (:query (first (vals (search-query xml))))]
(is (string-equal-ignore-whitespace actual expected))))
(testing "delete query generation - compound key"
(let [expected "-- :name delete-addres! :! :n\n-- :doc updates an existing addres record\nDELETE FROM address\nWHERE address.id = :id AND\n\taddress.postcode = ':postcode'\n\n"
actual (:query (first (vals (delete-query xml))))]
(is (string-equal-ignore-whitespace actual expected))))))