<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[13367] twext/trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/13367">13367</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-04-24 09:10:35 -0700 (Thu, 24 Apr 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Use latest sqlparse which supports Oracle syntax. Fix parsing and schema compare to work with more
complex postgres and Oracle behaviors.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#twexttrunksetuppy">twext/trunk/setup.py</a></li>
<li><a href="#twexttrunktwextenterprisedalmodelpy">twext/trunk/twext/enterprise/dal/model.py</a></li>
<li><a href="#twexttrunktwextenterprisedalparseschemapy">twext/trunk/twext/enterprise/dal/parseschema.py</a></li>
<li><a href="#twexttrunktwextenterprisedaltesttest_parseschemapy">twext/trunk/twext/enterprise/dal/test/test_parseschema.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="twexttrunksetuppy"></a>
<div class="modfile"><h4>Modified: twext/trunk/setup.py (13366 => 13367)</h4>
<pre class="diff"><span>
<span class="info">--- twext/trunk/setup.py        2014-04-24 04:27:28 UTC (rev 13366)
+++ twext/trunk/setup.py        2014-04-24 16:10:35 UTC (rev 13367)
</span><span class="lines">@@ -38,6 +38,7 @@
</span><span class="cx">     return modules + setuptools_find_packages()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def version():
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Compute the version number.
</span><span class="lines">@@ -142,7 +143,7 @@
</span><span class="cx"> 
</span><span class="cx"> extras_requirements = {
</span><span class="cx">     # Database Abstraction Layer
</span><del>-    &quot;DAL&quot;: [&quot;sqlparse==0.1.2&quot;],
</del><ins>+    &quot;DAL&quot;: [&quot;sqlparse&gt;=0.1.11&quot;],
</ins><span class="cx"> 
</span><span class="cx">     # LDAP
</span><span class="cx">     &quot;LDAP&quot;: [&quot;python-ldap&quot;],
</span><span class="lines">@@ -177,6 +178,7 @@
</span><span class="cx">         pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> #
</span><span class="cx"> # Run setup
</span><span class="cx"> #
</span></span></pre></div>
<a id="twexttrunktwextenterprisedalmodelpy"></a>
<div class="modfile"><h4>Modified: twext/trunk/twext/enterprise/dal/model.py (13366 => 13367)</h4>
<pre class="diff"><span>
<span class="info">--- twext/trunk/twext/enterprise/dal/model.py        2014-04-24 04:27:28 UTC (rev 13366)
+++ twext/trunk/twext/enterprise/dal/model.py        2014-04-24 16:10:35 UTC (rev 13367)
</span><span class="lines">@@ -59,11 +59,12 @@
</span><span class="cx"> 
</span><span class="cx">     def __eq__(self, other):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Compare equal to other L{SQLTypes} with matching name and length.
</del><ins>+        Compare equal to other L{SQLTypes} with matching name and length. The name is
+        normalized so we can compare schema from different types of DB implementations.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if not isinstance(other, SQLType):
</span><span class="cx">             return NotImplemented
</span><del>-        return (self.name, self.length) == (other.name, other.length)
</del><ins>+        return (self.normalizedName(), self.length) == (other.normalizedName(), other.length)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def __ne__(self, other):
</span><span class="lines">@@ -87,7 +88,21 @@
</span><span class="cx">         return &quot;&lt;SQL Type: %r%s&gt;&quot; % (self.name, lendesc)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def normalizedName(self):
+        &quot;&quot;&quot;
+        Map type names to standard names.
+        &quot;&quot;&quot;
+        return {
+            &quot;nchar&quot;: &quot;char&quot;,
+            &quot;varchar2&quot;: &quot;varchar&quot;,
+            &quot;nvarchar2&quot;: &quot;varchar&quot;,
+            &quot;clob&quot;: &quot;text&quot;,
+            &quot;nclob&quot;: &quot;text&quot;,
+            &quot;boolean&quot;: &quot;integer&quot;,
+        }.get(self.name, self.name)
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class Constraint(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     A constraint on a set of columns.
</span><span class="lines">@@ -131,11 +146,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class ProcedureCall(object):
</del><ins>+class ProcedureCall(FancyEqMixin):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     An invocation of a stored procedure or built-in function.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+    compareAttributes = 'name args'.split()
+
</ins><span class="cx">     def __init__(self, name, args):
</span><span class="cx">         _checkstr(name)
</span><span class="cx">         self.name = name
</span><span class="lines">@@ -161,6 +178,19 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def listIfNone(x):
+    return [] if x is None else x
+
+
+
+def stringIfNone(x, attr=None):
+    if attr:
+        return &quot;&quot; if x is None else getattr(x, attr)
+    else:
+        return &quot;&quot; if x is None else x
+
+
+
</ins><span class="cx"> class Column(FancyEqMixin, object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     A column from a table.
</span><span class="lines">@@ -215,14 +245,23 @@
</span><span class="cx"> 
</span><span class="cx">         results = []
</span><span class="cx"> 
</span><del>-        # TODO: sql_dump does not do types write now - so ignore this
-        # if self.type != other.type:
-        #     results.append(
-        #         &quot;Table: %s, mismatched column type: %s&quot;
-        #         % (self.table.name, self.name)
-        #     )
-
-        # TODO: figure out how to compare default, references and deleteAction
</del><ins>+        if self.name != other.name:
+            results.append(&quot;Table: %s, column names %s and %s do not match&quot; % (self.table.name, self.name, other.name,))
+        if self.type != other.type:
+            results.append(&quot;Table: %s, column name %s type mismatch&quot; % (self.table.name, self.name,))
+        if self.default != other.default:
+            # Some DBs don't allow sequence as a default
+            if (
+                isinstance(self.default, Sequence) and other.default == NO_DEFAULT or
+                self.default == NO_DEFAULT and isinstance(other.default, Sequence)
+            ):
+                pass
+            else:
+                results.append(&quot;Table: %s, column name %s default mismatch&quot; % (self.table.name, self.name,))
+        if stringIfNone(self.references, &quot;name&quot;) != stringIfNone(other.references, &quot;name&quot;):
+            results.append(&quot;Table: %s, column name %s references mismatch&quot; % (self.table.name, self.name,))
+        if self.deleteAction != other.deleteAction:
+            results.append(&quot;Table: %s, column name %s delete action mismatch&quot; % (self.table.name, self.name,))
</ins><span class="cx">         return results
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -333,7 +372,18 @@
</span><span class="cx">         for name in set(myColumns.keys()) &amp; set(otherColumns.keys()):
</span><span class="cx">             results.extend(myColumns[name].compare(otherColumns[name]))
</span><span class="cx"> 
</span><del>-        # TODO: figure out how to compare schemaRows
</del><ins>+        if not all([len(a.compare(b)) == 0 for a, b in zip(
+            listIfNone(self.primaryKey),
+            listIfNone(other.primaryKey),
+        )]):
+            results.append(&quot;Table: %s, mismatched primary key&quot; % (self.name,))
+
+        for myRow, otherRow in zip(self.schemaRows, other.schemaRows):
+            myRows = dict([(column.name, value) for column, value in myRow.items()])
+            otherRows = dict([(column.name, value) for column, value in otherRow.items()])
+            if myRows != otherRows:
+                results.append(&quot;Table: %s, mismatched schema rows: %s&quot; % (self.name, myRows))
+
</ins><span class="cx">         return results
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -392,7 +442,7 @@
</span><span class="cx">         self.constraints.append(Check(protoExpression, name))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def insertSchemaRow(self, values):
</del><ins>+    def insertSchemaRow(self, values, columns=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         A statically-defined row was inserted as part of the schema itself.
</span><span class="cx">         This is used for tables that want to track static enumerations, for
</span><span class="lines">@@ -405,9 +455,12 @@
</span><span class="cx"> 
</span><span class="cx">         @param values: a C{list} of data items, one for each column in this
</span><span class="cx">             table's current list of L{Column}s.
</span><ins>+        @param columns: a C{list} of column names to insert into. If C{None}
+            then use all table columns.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         row = {}
</span><del>-        for column, value in zip(self.columns, values):
</del><ins>+        columns = self.columns if columns is None else [self.columnNamed(name) for name in columns]
+        for column, value in zip(columns, values):
</ins><span class="cx">             row[column] = value
</span><span class="cx">         self.schemaRows.append(row)
</span><span class="cx"> 
</span></span></pre></div>
<a id="twexttrunktwextenterprisedalparseschemapy"></a>
<div class="modfile"><h4>Modified: twext/trunk/twext/enterprise/dal/parseschema.py (13366 => 13367)</h4>
<pre class="diff"><span>
<span class="info">--- twext/trunk/twext/enterprise/dal/parseschema.py        2014-04-24 04:27:28 UTC (rev 13366)
+++ twext/trunk/twext/enterprise/dal/parseschema.py        2014-04-24 16:10:35 UTC (rev 13367)
</span><span class="lines">@@ -209,17 +209,23 @@
</span><span class="cx">                     idx.addColumn(idx.table.columnNamed(columnName))
</span><span class="cx"> 
</span><span class="cx">             elif createType == u&quot;FUNCTION&quot;:
</span><del>-                FunctionModel(
-                    schema,
-                    stmt.token_next(2, True).value.encode(&quot;utf-8&quot;)
-                )
</del><ins>+                parseFunction(schema, stmt)
</ins><span class="cx"> 
</span><span class="cx">         elif stmt.get_type() == &quot;INSERT&quot;:
</span><span class="cx">             insertTokens = iterSignificant(stmt)
</span><span class="cx">             expect(insertTokens, ttype=Keyword.DML, value=&quot;INSERT&quot;)
</span><span class="cx">             expect(insertTokens, ttype=Keyword, value=&quot;INTO&quot;)
</span><span class="cx"> 
</span><del>-            tableName = expect(insertTokens, cls=Identifier).get_name()
</del><ins>+            token = insertTokens.next()
+
+            if isinstance(token, Function):
+                [tableName, columnArgs] = iterSignificant(token)
+                tableName = tableName.get_name()
+                columns = namesInParens(columnArgs)
+            else:
+                tableName = token.get_name()
+                columns = None
+
</ins><span class="cx">             expect(insertTokens, ttype=Keyword, value=&quot;VALUES&quot;)
</span><span class="cx"> 
</span><span class="cx">             values = expect(insertTokens, cls=Parenthesis)
</span><span class="lines">@@ -238,16 +244,13 @@
</span><span class="cx">                     [ident.ttype](ident.value)
</span><span class="cx">                 )
</span><span class="cx"> 
</span><del>-            schema.tableNamed(tableName).insertSchemaRow(rowData)
</del><ins>+            schema.tableNamed(tableName).insertSchemaRow(rowData, columns=columns)
</ins><span class="cx"> 
</span><span class="cx">         elif stmt.get_type() == &quot;CREATE OR REPLACE&quot;:
</span><span class="cx">             createType = stmt.token_next(1, True).value.upper()
</span><span class="cx"> 
</span><span class="cx">             if createType == u&quot;FUNCTION&quot;:
</span><del>-                FunctionModel(
-                    schema,
-                    stmt.token_next(2, True).token_first(True).token_first(True).value.encode(&quot;utf-8&quot;)
-                )
</del><ins>+                parseFunction(schema, stmt)
</ins><span class="cx"> 
</span><span class="cx">         else:
</span><span class="cx">             print(&quot;unknown type:&quot;, stmt.get_type())
</span><span class="lines">@@ -256,6 +259,25 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def parseFunction(schema, stmt):
+    &quot;&quot;&quot;
+    A FUNCTION may or may not have an argument list, so we need to account for
+    both possibilities.
+    &quot;&quot;&quot;
+    fn_name = stmt.token_next(2, True)
+    if isinstance(fn_name, Function):
+        [fn_name, _ignore_args] = iterSignificant(fn_name)
+        fn_name = fn_name.get_name()
+    else:
+        fn_name = fn_name.get_name()
+
+    FunctionModel(
+        schema,
+        fn_name.encode(&quot;utf-8&quot;),
+    )
+
+
+
</ins><span class="cx"> class _ColumnParser(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Stateful parser for the things between commas.
</span><span class="lines">@@ -326,22 +348,6 @@
</span><span class="cx">             return self.parseConstraint(maybeIdent)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def namesInParens(self, parens):
-        parens = iterSignificant(parens)
-        expect(parens, ttype=Punctuation, value=&quot;(&quot;)
-        idorids = parens.next()
-
-        if isinstance(idorids, Identifier):
-            idnames = [idorids.get_name()]
-        elif isinstance(idorids, IdentifierList):
-            idnames = [x.get_name() for x in idorids.get_identifiers()]
-        else:
-            raise ViolatedExpectation(&quot;identifier or list&quot;, repr(idorids))
-
-        expect(parens, ttype=Punctuation, value=&quot;)&quot;)
-        return idnames
-
-
</del><span class="cx">     def readExpression(self, parens):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Read a given expression from a Parenthesis object.  (This is currently
</span><span class="lines">@@ -369,7 +375,7 @@
</span><span class="cx">             funcName = expect(parens, ttype=Keyword).value.encode(&quot;ascii&quot;)
</span><span class="cx">             rhs = FunctionSyntax(funcName)(*[
</span><span class="cx">                 ColumnSyntax(self.table.columnNamed(x)) for x in
</span><del>-                self.namesInParens(expect(parens, cls=Parenthesis))
</del><ins>+                namesInParens(expect(parens, cls=Parenthesis))
</ins><span class="cx">             ])
</span><span class="cx">             result = CompoundComparison(lhs, op, rhs)
</span><span class="cx"> 
</span><span class="lines">@@ -404,10 +410,10 @@
</span><span class="cx"> 
</span><span class="cx">         if constraintType.match(Keyword, &quot;PRIMARY&quot;):
</span><span class="cx">             expect(self, ttype=Keyword, value=&quot;KEY&quot;)
</span><del>-            names = self.namesInParens(expect(self, cls=Parenthesis))
</del><ins>+            names = namesInParens(expect(self, cls=Parenthesis))
</ins><span class="cx">             self.table.primaryKey = [self.table.columnNamed(n) for n in names]
</span><span class="cx">         elif constraintType.match(Keyword, &quot;UNIQUE&quot;):
</span><del>-            names = self.namesInParens(expect(self, cls=Parenthesis))
</del><ins>+            names = namesInParens(expect(self, cls=Parenthesis))
</ins><span class="cx">             self.table.tableConstraint(Constraint.UNIQUE, names)
</span><span class="cx">         elif constraintType.match(Keyword, &quot;CHECK&quot;):
</span><span class="cx">             self.table.checkConstraint(self.readExpression(self.next()), ident)
</span><span class="lines">@@ -519,7 +525,7 @@
</span><span class="cx">                             defaultValue.referringColumns.append(theColumn)
</span><span class="cx">                         else:
</span><span class="cx">                             defaultValue = ProcedureCall(
</span><del>-                                thingo.encode(&quot;utf-8&quot;), parens
</del><ins>+                                thingo.encode(&quot;utf-8&quot;), namesInParens(parens),
</ins><span class="cx">                             )
</span><span class="cx"> 
</span><span class="cx">                     elif theDefault.ttype == Number.Integer:
</span><span class="lines">@@ -546,6 +552,17 @@
</span><span class="cx">                     elif theDefault.ttype == String.Single:
</span><span class="cx">                         defaultValue = _destringify(theDefault.value)
</span><span class="cx"> 
</span><ins>+                    # Oracle format for current timestamp mapped to postgres variant
+                    elif (
+                        theDefault.ttype == Keyword and
+                        theDefault.value.lower() == &quot;current_timestamp&quot;
+                    ):
+                        expect(self, ttype=Keyword, value=&quot;at&quot;)
+                        expect(self, ttype=None, value=&quot;time&quot;)
+                        expect(self, ttype=None, value=&quot;zone&quot;)
+                        expect(self, ttype=String.Single, value=&quot;'UTC'&quot;)
+                        defaultValue = ProcedureCall(&quot;timezone&quot;, [u&quot;UTC&quot;, u&quot;CURRENT_TIMESTAMP&quot;])
+
</ins><span class="cx">                     else:
</span><span class="cx">                         raise RuntimeError(
</span><span class="cx">                             &quot;not sure what to do: default %r&quot;
</span><span class="lines">@@ -635,11 +652,35 @@
</span><span class="cx">         return token.get_name()
</span><span class="cx">     elif token.ttype == Name:
</span><span class="cx">         return token.value
</span><ins>+    elif token.ttype == String.Single:
+        return _destringify(token.value)
+    elif token.ttype == Keyword:
+        return token.value
</ins><span class="cx">     else:
</span><span class="cx">         raise ViolatedExpectation(&quot;identifier or name&quot;, repr(token))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def namesInParens(parens):
+    parens = iterSignificant(parens)
+    expect(parens, ttype=Punctuation, value=&quot;(&quot;)
+    idorids = parens.next()
+
+    if isinstance(idorids, Identifier):
+        idnames = [idorids.get_name()]
+    elif isinstance(idorids, IdentifierList):
+        idnames = [nameOrIdentifier(x) for x in idorids.get_identifiers()]
+    elif idorids.ttype == String.Single:
+        idnames = [nameOrIdentifier(idorids)]
+    else:
+        expectSingle(idorids, ttype=Punctuation, value=&quot;)&quot;)
+        return []
+
+    expect(parens, ttype=Punctuation, value=&quot;)&quot;)
+    return idnames
+
+
+
</ins><span class="cx"> def expectSingle(nextval, ttype=None, value=None, cls=None):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Expect some properties from retrieved value.
</span></span></pre></div>
<a id="twexttrunktwextenterprisedaltesttest_parseschemapy"></a>
<div class="modfile"><h4>Modified: twext/trunk/twext/enterprise/dal/test/test_parseschema.py (13366 => 13367)</h4>
<pre class="diff"><span>
<span class="info">--- twext/trunk/twext/enterprise/dal/test/test_parseschema.py        2014-04-24 04:27:28 UTC (rev 13366)
+++ twext/trunk/twext/enterprise/dal/test/test_parseschema.py        2014-04-24 16:10:35 UTC (rev 13367)
</span><span class="lines">@@ -19,7 +19,7 @@
</span><span class="cx"> and L{twext.enterprise.dal.parseschema}.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from twext.enterprise.dal.model import Schema
</del><ins>+from twext.enterprise.dal.model import Schema, ProcedureCall
</ins><span class="cx"> from twext.enterprise.dal.syntax import CompoundComparison, ColumnSyntax
</span><span class="cx"> 
</span><span class="cx"> try:
</span><span class="lines">@@ -176,6 +176,29 @@
</span><span class="cx">         self.assertEquals(table.columnNamed(&quot;f&quot;).default, None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def test_defaultFunctionColumns(self):
+        &quot;&quot;&quot;
+        Parsing a 'default' column with a function call in it will return
+        that function as the 'default' attribute of the Column object.
+        &quot;&quot;&quot;
+        s = self.schemaFromString(
+            &quot;&quot;&quot;
+            create table a (
+                b1 integer default tz(),
+                b2 integer default tz('UTC'),
+                b3 integer default tz('UTC', 'GMT'),
+                b4 integer default timezone('UTC', CURRENT_TIMESTAMP),
+                b5 integer default CURRENT_TIMESTAMP at time zone 'UTC'
+            );
+            &quot;&quot;&quot;)
+        table = s.tableNamed(&quot;a&quot;)
+        self.assertEquals(table.columnNamed(&quot;b1&quot;).default, ProcedureCall(&quot;tz&quot;, []))
+        self.assertEquals(table.columnNamed(&quot;b2&quot;).default, ProcedureCall(&quot;tz&quot;, [&quot;UTC&quot;]))
+        self.assertEquals(table.columnNamed(&quot;b3&quot;).default, ProcedureCall(&quot;tz&quot;, [&quot;UTC&quot;, &quot;GMT&quot;]))
+        self.assertEquals(table.columnNamed(&quot;b4&quot;).default, ProcedureCall(&quot;timezone&quot;, [&quot;UTC&quot;, &quot;CURRENT_TIMESTAMP&quot;]))
+        self.assertEquals(table.columnNamed(&quot;b5&quot;).default, ProcedureCall(&quot;timezone&quot;, [&quot;UTC&quot;, &quot;CURRENT_TIMESTAMP&quot;]))
+
+
</ins><span class="cx">     def test_needsValue(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Columns with defaults, or with a 'not null' constraint don't need a
</span><span class="lines">@@ -333,7 +356,7 @@
</span><span class="cx">             &quot;&quot;&quot;
</span><span class="cx">             create table a1 (b1 integer primary key);
</span><span class="cx">             create table c2 (d2 integer references a1 on delete cascade);
</span><del>-            create table e3 (f3 integer references a1 on delete set null);
</del><ins>+            create table ee3 (f3 integer references a1 on delete set null);
</ins><span class="cx">             create table g4 (h4 integer references a1 on delete set default);
</span><span class="cx">             &quot;&quot;&quot;
</span><span class="cx">         )
</span><span class="lines">@@ -346,7 +369,7 @@
</span><span class="cx">             &quot;cascade&quot;
</span><span class="cx">         )
</span><span class="cx">         self.assertEquals(
</span><del>-            s.tableNamed(&quot;e3&quot;).columnNamed(&quot;f3&quot;).deleteAction,
</del><ins>+            s.tableNamed(&quot;ee3&quot;).columnNamed(&quot;f3&quot;).deleteAction,
</ins><span class="cx">             &quot;set null&quot;
</span><span class="cx">         )
</span><span class="cx">         self.assertEquals(
</span><span class="lines">@@ -388,7 +411,7 @@
</span><span class="cx">             &quot;&quot;&quot;
</span><span class="cx">             create table q (b integer); -- noise
</span><span class="cx">             create table a (b integer primary key, c integer);
</span><del>-            create table z (c integer, unique(c) );
</del><ins>+            create table z (c integer, unique (c) );
</ins><span class="cx"> 
</span><span class="cx">             create unique index idx_a_c on a(c);
</span><span class="cx">             create index idx_a_b_c on a (c, b);
</span><span class="lines">@@ -417,13 +440,47 @@
</span><span class="cx">     RETURN i + 1;
</span><span class="cx"> END;
</span><span class="cx"> $$ LANGUAGE plpgsql;
</span><ins>+CREATE FUNCTION autoincrement RETURNS integer AS $$
+BEGIN
+    RETURN 1;
+END;
+$$ LANGUAGE plpgsql;
</ins><span class="cx"> CREATE OR REPLACE FUNCTION decrement(i integer) RETURNS integer AS $$
</span><span class="cx"> BEGIN
</span><span class="cx">     RETURN i - 1;
</span><span class="cx"> END;
</span><span class="cx"> $$ LANGUAGE plpgsql;
</span><ins>+CREATE OR REPLACE FUNCTION autodecrement (i integer) RETURNS integer AS $$
+BEGIN
+    RETURN i - 1;
+END;
+$$ LANGUAGE plpgsql;
</ins><span class="cx">             &quot;&quot;&quot;
</span><span class="cx">         )
</span><span class="cx">         self.assertTrue(s.functionNamed(&quot;increment&quot;) is not None)
</span><span class="cx">         self.assertTrue(s.functionNamed(&quot;decrement&quot;) is not None)
</span><span class="cx">         self.assertRaises(KeyError, s.functionNamed, &quot;merge&quot;)
</span><ins>+
+
+    def test_insert(self):
+        &quot;&quot;&quot;
+        An 'insert' statement will add an L{schemaRows} to an L{Table}.
+        &quot;&quot;&quot;
+        s = self.schemaFromString(
+            &quot;&quot;&quot;
+            create table alpha (beta integer, gamma integer not null);
+
+            insert into alpha values (1, 2);
+            insert into alpha (gamma, beta) values (3, 4);
+            &quot;&quot;&quot;
+        )
+        self.assertTrue(s.tableNamed(&quot;alpha&quot;) is not None)
+        self.assertEqual(len(s.tableNamed(&quot;alpha&quot;).schemaRows), 2)
+        rows = [[(column.name, value) for column, value in sorted(row.items(), key=lambda x:x[0])] for row in s.tableNamed(&quot;alpha&quot;).schemaRows]
+        self.assertEqual(
+            rows,
+            [
+                [(&quot;beta&quot;, 1), (&quot;gamma&quot;, 2)],
+                [(&quot;beta&quot;, 4), (&quot;gamma&quot;, 3)],
+            ]
+        )
</ins></span></pre>
</div>
</div>

</body>
</html>