Qstore: support LIMIT and OFFSET clauses.
authorscottmk <scottmk@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Tue, 13 Jul 2010 20:10:37 +0000 (20:10 +0000)
committerscottmk <scottmk@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Tue, 13 Jul 2010 20:10:37 +0000 (20:10 +0000)
Also: add some links to the IDL, that should have been there
in the first place.

M    Open-ILS/include/openils/oils_buildq.h
M    Open-ILS/src/c-apps/oils_storedq.c
M    Open-ILS/src/c-apps/buildSQL.c
M    Open-ILS/src/sql/Pg/002.schema.config.sql
M    Open-ILS/src/sql/Pg/008.schema.query.sql
A    Open-ILS/src/sql/Pg/upgrade/0336.schema.query-limit-offset-fkey.sql
M    Open-ILS/examples/fm_IDL.xml

git-svn-id: svn://svn.open-ils.org/ILS/trunk@16922 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/examples/fm_IDL.xml
Open-ILS/include/openils/oils_buildq.h
Open-ILS/src/c-apps/buildSQL.c
Open-ILS/src/c-apps/oils_storedq.c
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/008.schema.query.sql
Open-ILS/src/sql/Pg/upgrade/0336.schema.query-limit-offset-fkey.sql [new file with mode: 0644]

index d8c3b01..a6c308e 100644 (file)
@@ -6793,13 +6793,18 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field reporter:label="Query type" name="type" reporter:datatype="text"/>
                        <field reporter:label="Use ALL" name="use_all" reporter:datatype="bool"/>
                        <field reporter:label="Use DISTINCT" name="use_distinct" reporter:datatype="bool"/>
                        <field reporter:label="Query type" name="type" reporter:datatype="text"/>
                        <field reporter:label="Use ALL" name="use_all" reporter:datatype="bool"/>
                        <field reporter:label="Use DISTINCT" name="use_distinct" reporter:datatype="bool"/>
-                       <field reporter:label="FROM Clause" name="from_clause" reporter:datatype="int"/>
-                       <field reporter:label="WHERE Clause" name="where_clause" reporter:datatype="int"/>
-                       <field reporter:label="HAVING Clause" name="having_clause" reporter:datatype="int"/>
-                       <field reporter:label="LIMIT count" name="limit_count" reporter:datatype="int"/>
-                       <field reporter:label="OFFSET count" name="offset_count" reporter:datatype="int"/>
-               </fields>
-               <links>
+                       <field reporter:label="FROM Clause" name="from_clause" reporter:datatype="link"/>
+                       <field reporter:label="WHERE Clause" name="where_clause" reporter:datatype="link"/>
+                       <field reporter:label="HAVING Clause" name="having_clause" reporter:datatype="link"/>
+                       <field reporter:label="LIMIT count" name="limit_count" reporter:datatype="link"/>
+                       <field reporter:label="OFFSET count" name="offset_count" reporter:datatype="link"/>
+               </fields>
+               <links>
+                       <link field="from_clause" reltype="has_a" key="id" map="" class="qfr"/>
+                       <link field="having_clause" reltype="has_a" key="id" map="" class="qxp"/>
+                       <link field="where_clause" reltype="has_a" key="id" map="" class="qxp"/>
+                       <link field="limit_count" reltype="has_a" key="id" map="" class="qxp"/>
+                       <link field="offset_count" reltype="has_a" key="id" map="" class="qxp"/>
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                </permacrud>
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                </permacrud>
index a51c020..800e795 100644 (file)
@@ -90,6 +90,8 @@ struct StoredQ_ {
        QSeq*         child_list;
        Expression*   having_clause;
        OrderItem*    order_by_list;
        QSeq*         child_list;
        Expression*   having_clause;
        OrderItem*    order_by_list;
+       Expression*   limit_count;
+       Expression*   offset_count;
 };
 
 typedef enum {
 };
 
 typedef enum {
index e19d9fc..07f018f 100644 (file)
@@ -25,10 +25,12 @@ static void buildGroupBy( BuildSQLState* state, const SelectItem* sel_list );
 static void buildOrderBy( BuildSQLState* state, const OrderItem* ord_list );
 static void buildCase( BuildSQLState* state, const Expression* expr );
 static void buildExpression( BuildSQLState* state, const Expression* expr );
 static void buildOrderBy( BuildSQLState* state, const OrderItem* ord_list );
 static void buildCase( BuildSQLState* state, const Expression* expr );
 static void buildExpression( BuildSQLState* state, const Expression* expr );
+
 static void buildFunction( BuildSQLState* state, const Expression* exp );
 static int subexp_count( const Expression* expr );
 static void buildTypicalFunction( BuildSQLState* state, const Expression* expr );
 static void buildExtract( BuildSQLState* state, const Expression* expr );
 static void buildFunction( BuildSQLState* state, const Expression* exp );
 static int subexp_count( const Expression* expr );
 static void buildTypicalFunction( BuildSQLState* state, const Expression* expr );
 static void buildExtract( BuildSQLState* state, const Expression* expr );
+
 static void buildSeries( BuildSQLState* state, const Expression* subexp_list, const char* op );
 static void buildBindVar( BuildSQLState* state, const BindVar* bind );
 static void buildScalar( BuildSQLState* state, int numeric, const jsonObject* obj );
 static void buildSeries( BuildSQLState* state, const Expression* subexp_list, const char* op );
 static void buildBindVar( BuildSQLState* state, const BindVar* bind );
 static void buildScalar( BuildSQLState* state, int numeric, const jsonObject* obj );
@@ -262,7 +264,7 @@ static void buildSelect( BuildSQLState* state, const StoredQ* query ) {
                return;
        }
 
                return;
        }
 
-       // To do: get SELECT list; just a stub here
+       // Get SELECT list
        buffer_add( state->sql, "SELECT" );
        incr_indent( state );
        buildSelectList( state, query->select_list );
        buffer_add( state->sql, "SELECT" );
        incr_indent( state );
        buildSelectList( state, query->select_list );
@@ -326,9 +328,19 @@ static void buildSelect( BuildSQLState* state, const StoredQ* query ) {
                }
        }
 
                }
        }
 
-       // To do: Build LIMIT clause, if there is one
+       // Build LIMIT clause, if there is one
+       if( query->limit_count ) {
+               add_newline( state );
+               buffer_add( state->sql, "LIMIT " );
+               buildExpression( state, query->limit_count );
+       }
 
 
-       // To do: Build OFFSET clause, if there is one
+       // Build OFFSET clause, if there is one
+       if( query->offset_count ) {
+               add_newline( state );
+               buffer_add( state->sql, "OFFSET " );
+               buildExpression( state, query->offset_count );
+       }
 
        state->error = 0;
 }
 
        state->error = 0;
 }
@@ -968,7 +980,19 @@ static void buildFunction( BuildSQLState* state, const Expression* expr ) {
                buffer_add( state->sql, "LOCALTIME " );
        else if( !strcasecmp( expr->function_name, "LOCALTIMESTAMP" ) && ! expr->subexp_list )
                buffer_add( state->sql, "LOCALTIMESTAMP " );
                buffer_add( state->sql, "LOCALTIME " );
        else if( !strcasecmp( expr->function_name, "LOCALTIMESTAMP" ) && ! expr->subexp_list )
                buffer_add( state->sql, "LOCALTIMESTAMP " );
-       else
+       else if( !strcasecmp( expr->function_name, "TRIM" )) {
+               int arg_count = subexp_count( expr );
+
+               if( (arg_count != 2 && arg_count != 3 ) || expr->subexp_list->type != EXP_STRING )
+                       buildTypicalFunction( state, expr );
+               else {
+                       sqlAddMsg( state,
+                               "TRIM function not supported in expr # %d; use ltrim() and/or rtrim()",
+                               expr->id );
+                       state->error = 1;
+                       return;
+               }
+       } else
                buildTypicalFunction( state, expr );     // Not a special exception.
 
        if( expr->column_name ) {
                buildTypicalFunction( state, expr );     // Not a special exception.
 
        if( expr->column_name ) {
@@ -985,6 +1009,9 @@ static void buildFunction( BuildSQLState* state, const Expression* expr ) {
        @return The number of subexpressions.
 */
 static int subexp_count( const Expression* expr ) {
        @return The number of subexpressions.
 */
 static int subexp_count( const Expression* expr ) {
+       if( !expr )
+               return 0;
+
        int count = 0;
        const Expression* sub = expr->subexp_list;
        while( sub ) {
        int count = 0;
        const Expression* sub = expr->subexp_list;
        while( sub ) {
index 5258bff..9d9def4 100644 (file)
@@ -184,8 +184,8 @@ StoredQ* getStoredQuery( BuildSQLState* state, int query_id ) {
 
        StoredQ* sq = NULL;
        dbi_result result = dbi_conn_queryf( state->dbhandle,
 
        StoredQ* sq = NULL;
        dbi_result result = dbi_conn_queryf( state->dbhandle,
-               "SELECT id, type, use_all, use_distinct, from_clause, where_clause, having_clause "
-               "FROM query.stored_query WHERE id = %d;", query_id );
+               "SELECT id, type, use_all, use_distinct, from_clause, where_clause, having_clause, "
+               "limit_count, offset_count FROM query.stored_query WHERE id = %d;", query_id );
        if( result ) {
                if( dbi_result_first_row( result ) ) {
                        sq = constructStoredQ( state, result );
        if( result ) {
                if( dbi_result_first_row( result ) ) {
                        sq = constructStoredQ( state, result );
@@ -270,6 +270,18 @@ static StoredQ* constructStoredQ( BuildSQLState* state, dbi_result result ) {
        else
                having_clause_id = dbi_result_get_int_idx( result, 7 );
 
        else
                having_clause_id = dbi_result_get_int_idx( result, 7 );
 
+       int limit_count_id;
+       if( dbi_result_field_is_null_idx( result, 8 ) )
+               limit_count_id = -1;
+       else
+               limit_count_id = dbi_result_get_int_idx( result, 8 );
+
+       int offset_count_id;
+       if( dbi_result_field_is_null_idx( result, 9 ) )
+               offset_count_id = -1;
+       else
+               offset_count_id = dbi_result_get_int_idx( result, 9 );
+
        FromRelation* from_clause = NULL;
        if( QT_SELECT == type ) {
                // A SELECT query needs a FROM clause; go get it
        FromRelation* from_clause = NULL;
        if( QT_SELECT == type ) {
                // A SELECT query needs a FROM clause; go get it
@@ -327,6 +339,7 @@ static StoredQ* constructStoredQ( BuildSQLState* state, dbi_result result ) {
                }
        }
 
                }
        }
 
+       // Get the HAVING clause, if there is one
        Expression* having_clause = NULL;
        if( having_clause_id != -1 ) {
                having_clause = getExpression( state, having_clause_id );
        Expression* having_clause = NULL;
        if( having_clause_id != -1 ) {
                having_clause = getExpression( state, having_clause_id );
@@ -336,8 +349,8 @@ static StoredQ* constructStoredQ( BuildSQLState* state, dbi_result result ) {
                                "Unable to fetch HAVING expression for query id = %d", id ));
                        expressionFree( where_clause );
                        freeQSeqList( child_list );
                                "Unable to fetch HAVING expression for query id = %d", id ));
                        expressionFree( where_clause );
                        freeQSeqList( child_list );
-                       fromRelationFree( from_clause );
                        selectListFree( select_list );
                        selectListFree( select_list );
+                       fromRelationFree( from_clause );
                        state->error = 1;
                        return NULL;
                }
                        state->error = 1;
                        return NULL;
                }
@@ -351,11 +364,46 @@ static StoredQ* constructStoredQ( BuildSQLState* state, dbi_result result ) {
                expressionFree( having_clause );
                expressionFree( where_clause );
                freeQSeqList( child_list );
                expressionFree( having_clause );
                expressionFree( where_clause );
                freeQSeqList( child_list );
-               fromRelationFree( from_clause );
                selectListFree( select_list );
                selectListFree( select_list );
+               fromRelationFree( from_clause );
                return NULL;
        }
 
                return NULL;
        }
 
+       // Get the LIMIT clause, if there is one
+       Expression* limit_count = NULL;
+       if( limit_count_id != -1 ) {
+               limit_count = getExpression( state, limit_count_id );
+               if( ! limit_count ) {
+                       // shouldn't happen due to foreign key constraint
+                       osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                               "Unable to fetch LIMIT expression for query id = %d", id ));
+                       orderItemListFree( order_by_list );
+                       freeQSeqList( child_list );
+                       selectListFree( select_list );
+                       fromRelationFree( from_clause );
+                       state->error = 1;
+                       return NULL;
+               }
+       }
+
+       // Get the OFFSET clause, if there is one
+       Expression* offset_count = NULL;
+       if( offset_count_id != -1 ) {
+               offset_count = getExpression( state, offset_count_id );
+               if( ! offset_count ) {
+                       // shouldn't happen due to foreign key constraint
+                       osrfLogError( OSRF_LOG_MARK, sqlAddMsg( state,
+                               "Unable to fetch OFFSET expression for query id = %d", id ));
+                       expressionFree( limit_count );
+                       orderItemListFree( order_by_list );
+                       freeQSeqList( child_list );
+                       selectListFree( select_list );
+                       fromRelationFree( from_clause );
+                       state->error = 1;
+                       return NULL;
+               }
+       }
+
        // Allocate a StoredQ: from the free list if possible, from the heap if necessary
 
        StoredQ* sq;
        // Allocate a StoredQ: from the free list if possible, from the heap if necessary
 
        StoredQ* sq;
@@ -378,6 +426,8 @@ static StoredQ* constructStoredQ( BuildSQLState* state, dbi_result result ) {
        sq->child_list = child_list;
        sq->having_clause = having_clause;
        sq->order_by_list = order_by_list;
        sq->child_list = child_list;
        sq->having_clause = having_clause;
        sq->order_by_list = order_by_list;
+       sq->limit_count = limit_count;
+       sq->offset_count = offset_count;
 
        return sq;
 }
 
        return sq;
 }
@@ -535,8 +585,18 @@ void storedQFree( StoredQ* sq ) {
                        orderItemListFree( sq->order_by_list );
                        sq->order_by_list = NULL;
                }
                        orderItemListFree( sq->order_by_list );
                        sq->order_by_list = NULL;
                }
-               if( sq->having_clause )
+               if( sq->having_clause ) {
                        expressionFree( sq->having_clause );
                        expressionFree( sq->having_clause );
+                       sq->having_clause = NULL;
+               }
+               if( sq->limit_count ) {
+                       expressionFree( sq->limit_count );
+                       sq->limit_count = NULL;
+               }
+               if( sq->offset_count ) {
+                       expressionFree( sq->offset_count );
+                       sq->offset_count = NULL;
+               }
 
                // Stick the empty husk on the free list for potential reuse
                sq->next = free_storedq_list;
 
                // Stick the empty husk on the free list for potential reuse
                sq->next = free_storedq_list;
index ecf1979..8675e4d 100644 (file)
@@ -68,7 +68,7 @@ CREATE TABLE config.upgrade_log (
     install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
 );
 
     install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
 );
 
-INSERT INTO config.upgrade_log (version) VALUES ('0335'); -- dbs
+INSERT INTO config.upgrade_log (version) VALUES ('0336'); -- Scott McKellar
 
 CREATE TABLE config.bib_source (
        id              SERIAL  PRIMARY KEY,
 
 CREATE TABLE config.bib_source (
        id              SERIAL  PRIMARY KEY,
index 8c511f4..7f489a0 100644 (file)
@@ -39,9 +39,11 @@ CREATE TABLE  query.stored_query (
        where_clause  INT,           --REFERENCES query.expression
                                     --DEFERRABLE INITIALLY DEFERRED,
        having_clause INT,           --REFERENCES query.expression
        where_clause  INT,           --REFERENCES query.expression
                                     --DEFERRABLE INITIALLY DEFERRED,
        having_clause INT,           --REFERENCES query.expression
+                                    --DEFERRABLE INITIALLY DEFERRED,
+       limit_count   INT,           --REFERENCES query.expression
+                                    --DEFERRABLE INITIALLY DEFERRED,
+       offset_count  INT            --REFERENCES query.expression
                                     --DEFERRABLE INITIALLY DEFERRED
                                     --DEFERRABLE INITIALLY DEFERRED
-       limit_count   INT,
-       offset_count  INT
 );
 
 -- (Foreign keys to be defined later after other tables are created)
 );
 
 -- (Foreign keys to be defined later after other tables are created)
@@ -188,6 +190,16 @@ ALTER TABLE query.stored_query
        REFERENCES query.expression( id )
        DEFERRABLE INITIALLY DEFERRED;
 
        REFERENCES query.expression( id )
        DEFERRABLE INITIALLY DEFERRED;
 
+ALTER TABLE query.stored_query
+       ADD FOREIGN KEY ( limit_count )
+       REFERENCES query.expression( id )
+       DEFERRABLE INITIALLY DEFERRED;
+
+ALTER TABLE query.stored_query
+       ADD FOREIGN KEY ( offset_count )
+       REFERENCES query.expression( id )
+       DEFERRABLE INITIALLY DEFERRED;
+
 CREATE TABLE query.case_branch (
        id            SERIAL        PRIMARY KEY,
        parent_expr   INT           NOT NULL REFERENCES query.expression
 CREATE TABLE query.case_branch (
        id            SERIAL        PRIMARY KEY,
        parent_expr   INT           NOT NULL REFERENCES query.expression
diff --git a/Open-ILS/src/sql/Pg/upgrade/0336.schema.query-limit-offset-fkey.sql b/Open-ILS/src/sql/Pg/upgrade/0336.schema.query-limit-offset-fkey.sql
new file mode 100644 (file)
index 0000000..b0263b0
--- /dev/null
@@ -0,0 +1,15 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0336'); -- Scott McKellar
+
+ALTER TABLE query.stored_query
+    ADD FOREIGN KEY ( limit_count )
+    REFERENCES query.expression( id )
+    DEFERRABLE INITIALLY DEFERRED;
+
+ALTER TABLE query.stored_query
+    ADD FOREIGN KEY ( offset_count )
+    REFERENCES query.expression( id )
+    DEFERRABLE INITIALLY DEFERRED;
+
+COMMIT;