Module: sql-odbc-implementation Author: eec Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND // $HopeName: !introspection.dylan(D-kan.7) $ //define constant $introspection-rowset-size :: = 10; define class (, , ) end class; define method catalog-generator(result-set :: ) let stmt :: = result-set.%sql-statement; let return-code = nice-SQLTables(stmt.%statement-handle, $sql-all-catalogs, "", "", ""); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); end method; /* Note: these liaison functions call each other until they finally bottom out; the idea here is to get the data for a catalog, say, lazily, rather than going and getting all of the catalog data, schema data, table data, etc.. So, when a catalog record is returned, the liaison function makes an object. This in itself has a liaison function, because it is a result set, and its liaison function (the one that will be applied to the records that are of type schema) has to know about this instance, so we create the function with curry, so that this instance will be passed into that liaison function, should anyone ever attempt to index into the catalog collection. Similarly, the schema has many tables in it has its elements, and so its liaison function must be created to pass in the particular instance of the schema to the table creation function (table-liaison). Its turtles all the way down. */ define method catalog-liaison(record :: ) => (catalog :: ) let catalog = make(, filtered-source: #"self", name: record[0], generator: schema-generator, rowset-size: record.statement.connection.dbms.%dbms-rowset-size, // rowsets aren't working. $introspection-rowset-size, filter: rcurry(schema-filter, pair(#f, #())), connection: record.statement.connection); catalog.liaison := rcurry(schema-liaison, catalog); catalog; end method; define method catalog-from-name(a-connection :: , catalog-name :: ) => (catalog :: ) let stmt = make(, connection: a-connection, text: ""); let return-code = SQLSetStmtOption(stmt.%statement-handle, $SQL-ROWSET-SIZE, a-connection.dbms.%dbms-rowset-size); // rowsets aren't working $introspection-rowset-size); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); let return-code = nice-SQLTables(stmt.%statement-handle, catalog-name, "", "", ""); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); // The column count returned by SQLNumResultCols is ODBC's way // of checking if the executed sql statement returns a result-set. let (return-code, column-count) = SQLNumResultCols(stmt.%statement-handle); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); if (column-count = 0) signal(make(, catalog-name: catalog-name)); else let catalog = make(, filtered-source: #"self", connection: a-connection, name: catalog-name, generator: schema-generator, rowset-size: a-connection.dbms.%dbms-rowset-size, // rowsets aren't working $introspection-rowset-size, filter: rcurry(schema-filter, pair(#f, #()))); catalog.liaison := rcurry(schema-liaison, catalog); catalog; end if; end method; define method catalogs-assist(the-connection :: ) => (result-set :: ) make(, generator: catalog-generator, connection: the-connection, rowset-size: the-connection.dbms.%dbms-rowset-size, // rowsets aren't working $introspection-rowset-size, result-set-policy: make(, rowset-size: the-connection.dbms.%dbms-rowset-size), liaison: catalog-liaison); end method; define class (, ) // This slot isn't being used by anyone. // constant slot schema-catalog :: false-or() = #f, // init-keyword: schema-catalog:; end class; define method schema-generator(catalog :: ) => () let stmt = catalog.%sql-statement; /* MS SQL Server doesn't support search qualifier for tables unless a null pointer is passed. Of course, the ODBC documentation says a wild card and null pointer are the same thing. */ let dbms-name = catalog.connection.dbms.%dbms-name; let table-qualifier = if (as-lowercase(dbms-name) = "microsoft sql server") #f else "" end if; let return-code = nice-SQLTables(stmt.%statement-handle, catalog.database-object-name, $sql-all-schemas, table-qualifier, #f); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); end method; /* --- schema-filter --- Filter functions return true if they intend to skip the object, and false if they intend to return the object. If the name changes, then we are returning false. The purpose of the schema-tracking pair is for initialization: if we have not yet seen any elements, then we have to return false (returning object), but we need to store the first element that we have seen, so that we can compare the name with the previous name. Does this actually work? It seems like if there were actually three schemas, then we would be returning more than one element for the second and third schemas. It seems like schema-tracking.tail needs to be changed whenever this function returns false (that is, schema.database-object-name ~= schema-tracking.tail.database-object-name). */ define method schema-filter(result-set :: , schema :: , schema-tracking :: ) => (filter? :: ) if (schema-tracking.head = #f) schema-tracking.tail := schema; schema-tracking.head := #t; #f else schema.database-object-name = schema-tracking.tail.database-object-name; end if; end method; define method schema-liaison(record :: , catalog :: ) => (schema :: ) let schema = make(, name: record[1], generator: rcurry(table-generator, catalog), rowset-size: record.statement.connection.dbms.%dbms-rowset-size, // rowsets aren't working $introspection-rowset-size, connection: record.statement.connection); schema.liaison := rcurry(table-liaison, catalog, schema); schema; end method; define method schema-from-name(a-connection :: , catalog-name :: , schema-name :: ) => (schema :: ) let stmt = make(, connection: a-connection, text: ""); let return-code = SQLSetStmtOption(stmt.%statement-handle, $SQL-ROWSET-SIZE, a-connection.dbms.%dbms-rowset-size); // rowsets aren't working $introspection-rowset-size); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); let return-code = nice-SQLTables(stmt.%statement-handle, catalog-name, schema-name, #f, #f); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); let (return-code, column-count) = SQLNumResultCols(stmt.%statement-handle); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); if (column-count = 0) signal(make(, schema-name: schema-name)); else let catalog = catalog-from-name(a-connection, catalog-name); let schema = make(, name: schema-name, connection: a-connection, rowset-size: a-connection.dbms.%dbms-rowset-size, // rowsets aren't working $introspection-rowset-size, generator: rcurry(table-generator, catalog)); schema.liaison := rcurry(table-liaison, catalog, schema); schema; end if; end method; define class (, ) end class; define method table-generator(schema :: , catalog :: ) => () let stmt = schema.%sql-statement; let return-code = nice-SQLTables(stmt.%statement-handle, catalog.database-object-name, schema.database-object-name, $sql-all-tables, #f); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); end method; define method table-liaison(record :: , catalog :: , schema :: ) => (table :: ) make(, name: record[2], generator: rcurry(column-generator, catalog, schema), connection: record.statement.connection, rowset-size: record.statement.connection.dbms.%dbms-rowset-size, // rowsets aren't working $introspection-rowset-size, liaison: column-liaison); end method; define method table-from-name(a-connection :: , catalog-name :: , schema-name :: , table-name :: ) => (table :: ) let stmt = make(, connection: a-connection, text: ""); let return-code = SQLSetStmtOption(stmt.%statement-handle, $SQL-ROWSET-SIZE, a-connection.dbms.%dbms-rowset-size); // rowsets aren't working $introspection-rowset-size); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); let return-code = nice-SQLTables(stmt.%statement-handle, catalog-name, schema-name, table-name, #f); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); let (return-code, column-count) = SQLNumResultCols(stmt.%statement-handle); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); if (column-count = 0) signal(make(, table-name: table-name)); else let catalog = catalog-from-name(a-connection, catalog-name); let schema = schema-from-name(a-connection, catalog-name, schema-name); make(, name: table-name, generator: rcurry(column-generator, catalog, schema), connection: a-connection, rowset-size: a-connection.dbms.%dbms-rowset-size, // rowsets aren't working $introspection-rowset-size, liaison: column-liaison); end if; end method; define class () end class; define method column-generator(table :: , catalog :: , schema :: ) => () let stmt = table.%sql-statement; let return-code = nice-SQLColumns(stmt.%statement-handle, catalog.database-object-name, schema.database-object-name, table.database-object-name, #f); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); end method; define method column-liaison(record :: ) => (column :: ) let column-name = record[3]; let data-type = record[4]; let nullable = record[10]; make(, name: column-name, domain: element($sql-datatype-table, data-type, default: unfound), nullable?: if (nullable > 0) #t else #f end if, default-value: #f); end method; define class () end class; define function new-index(collection :: , index-record :: ) => (result :: ) let catalog-name = index-record[0]; let schema-name = index-record[1]; let table-name = index-record[2]; let non-unique = index-record[3]; let index-name = index-record[5]; let column-name = index-record[8]; let table = table-from-name(index-record.statement.connection, catalog-name, schema-name, table-name); let index = make(, name: index-name, unique-index?: ~non-unique, indexed-table: table); index-grouper(collection, index, index-record); index; end function; define function index-finished?(collection :: , index :: , index-record :: ) => (result :: ) let index-name = index-record[5]; let done? = index-name ~= index.database-object-name; done?; end function; define function index-grouper(collection :: , index :: , index-record :: ) => () let name = index-record[8]; let column = make(, name: name); // table: current-index.table); push-last(index.fields, column); end function; define class (, , ) inherited slot filter, init-value: method(collection, index-record) let index-type = index-record[6]; index-type = $SQL-TABLE-STAT; end method; end class; define method indexes(table :: ) => (result-set :: ) generate-data(table); make(, filtered-source: #"self", grouped-source: #"self", new-group: new-index, group-finished?: index-finished?, grouper: index-grouper, generator: rcurry(index-generator, table), rowset-size: table.%sql-statement.connection.dbms.%dbms-rowset-size, // rowsets aren't working $introspection-rowset-size, connection: table.%sql-statement.connection); end method; define method index-generator(result-set :: , table :: ) let stmt = result-set.%sql-statement; let return-code = nice-SQLStatistics(stmt.%statement-handle, #f, #f, table.database-object-name, $SQL-INDEX-ALL, $SQL-QUICK); assert-odbc-goodness(return-code, stmt.connection.dbms.%environment-handle, stmt.connection.%connection-handle, stmt.%statement-handle); end method; define method default-value(col :: ) => result :: ; signal(make()); end method; define method constraints (table :: ) => result :: ; // only really I think it ought not to be a result set, I think it ought to // be a boring old vector, like indexes. signal(make()); // Call sqlprimarykeys to collect up a result set of primary keys. // Create an object which has a slot which contains this result set. // This object will be of type . // Next call sqlforeignkeys to collect up a result set of foreign keys. // Etc, ... of type . // Call sqlcolumns, or more likely just iterate over the table (whups, // don't those forward only result sets suck?) and fetch out the columns // whose .unique? is true. This is a . // We can't do checks or assertions. end method;