diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /connectivity/source/drivers/postgresql/pq_databasemetadata.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | connectivity/source/drivers/postgresql/pq_databasemetadata.cxx | 2511 |
1 files changed, 2511 insertions, 0 deletions
diff --git a/connectivity/source/drivers/postgresql/pq_databasemetadata.cxx b/connectivity/source/drivers/postgresql/pq_databasemetadata.cxx new file mode 100644 index 000000000..7da57d1b6 --- /dev/null +++ b/connectivity/source/drivers/postgresql/pq_databasemetadata.cxx @@ -0,0 +1,2511 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Effective License of whole file: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * Parts "Copyright by Sun Microsystems, Inc" prior to August 2011: + * + * The Contents of this file are made available subject to the terms of + * the GNU Lesser General Public License Version 2.1 + * + * Copyright: 2000 by Sun Microsystems, Inc. + * + * Contributor(s): Joerg Budischewski + * + * All parts contributed on or after August 2011: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Some portions were adapted from JDBC PostgreSQL driver: + * + * Copyright (c) 2004-2008, PostgreSQL Global Development Group + * + * Licence of original JDBC driver code: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the PostgreSQL Global Development Group nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************/ + +#include <algorithm> +#include <string_view> + +#include <sal/log.hxx> +#include "pq_databasemetadata.hxx" +#include "pq_driver.hxx" +#include "pq_sequenceresultset.hxx" +#include "pq_statics.hxx" +#include "pq_tools.hxx" + +#include <o3tl/string_view.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/macros.h> +#include <com/sun/star/sdbc/TransactionIsolation.hpp> +#include <com/sun/star/sdbc/ResultSetType.hpp> +#include <com/sun/star/sdbc/XParameters.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/IndexType.hpp> +#include <com/sun/star/sdbc/ColumnValue.hpp> +#include <com/sun/star/sdbc/ColumnSearch.hpp> + +using ::osl::MutexGuard; + + +using namespace com::sun::star::sdbc; + +using com::sun::star::uno::Reference; +using com::sun::star::uno::Sequence; +using com::sun::star::uno::Any; +using com::sun::star::uno::UNO_QUERY; +using com::sun::star::uno::UNO_QUERY_THROW; + +namespace pq_sdbc_driver +{ +// These are pre-processor versions of KeyRule.idl declarations +// These are inherited from JDBC, and thus won't change anytime soon. +// Having them as pre-processor definitions allows to include them +// into compile-time strings (through SAL_STRINGIFY), which can be passed to ASCII_STR. +// That is without resorting to horrendous hacks in template meta-programming. +#define KEYRULE_CASCADE 0 +#define KEYRULE_RESTRICT 1 +#define KEYRULE_SET_NULL 2 +#define KEYRULE_NO_ACTION 4 +#define KEYRULE_SET_DEFAULT 4 +// Ditto for Deferrability.idl +#define DEFERRABILITY_INITIALLY_DEFERRED 5 +#define DEFERRABILITY_INITIALLY_IMMEDIATE 6 +#define DEFERRABILITY_NONE 7 + +DatabaseMetaData::DatabaseMetaData( + const ::rtl::Reference< comphelper::RefCountedMutex > & refMutex, + const css::uno::Reference< css::sdbc::XConnection > & origin, + ConnectionSettings *pSettings ) + : m_xMutex( refMutex ), + m_pSettings( pSettings ), + m_origin( origin ), + m_getIntSetting_stmt ( m_origin->prepareStatement("SELECT setting FROM pg_catalog.pg_settings WHERE name=?") ) +{ + init_getReferences_stmt(); + init_getPrivs_stmt(); +} + +sal_Bool DatabaseMetaData::allProceduresAreCallable( ) +{ + // TODO + return false; +} + +sal_Bool DatabaseMetaData::allTablesAreSelectable( ) +{ + return true; +} + +OUString DatabaseMetaData::getURL( ) +{ + // TODO + // LEM TODO: implement + return OUString(); +} + +OUString DatabaseMetaData::getUserName( ) +{ + return m_pSettings->user; +} + +sal_Bool DatabaseMetaData::isReadOnly( ) +{ + return false; +} + + +sal_Bool DatabaseMetaData::nullsAreSortedHigh( ) +{ + // Whether NULL values are considered, for sorting purposes, LARGER than any other value. + // Specification: http://download.oracle.com/javase/6/docs/api/java/sql/DatabaseMetaData.html#nullsAreSortedHigh() + // PostgreSQL behaviour: http://www.postgresql.org/docs/9.1/static/queries-order.html + return true; +} + +sal_Bool DatabaseMetaData::nullsAreSortedLow( ) +{ + return ! nullsAreSortedHigh(); +} + +sal_Bool DatabaseMetaData::nullsAreSortedAtStart( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::nullsAreSortedAtEnd( ) +{ + return false; +} + +OUString DatabaseMetaData::getDatabaseProductName( ) +{ + return "PostgreSQL"; +} + +OUString DatabaseMetaData::getDatabaseProductVersion( ) +{ + return OUString::createFromAscii( PQparameterStatus( m_pSettings->pConnection, "server_version" ) ); +} +OUString DatabaseMetaData::getDriverName( ) +{ + return "postgresql-sdbc"; +} + +OUString DatabaseMetaData::getDriverVersion( ) +{ + return PQ_SDBC_DRIVER_VERSION; +} + +sal_Int32 DatabaseMetaData::getDriverMajorVersion( ) +{ + return PQ_SDBC_MAJOR; +} + +sal_Int32 DatabaseMetaData::getDriverMinorVersion( ) +{ + return PQ_SDBC_MINOR; +} + +sal_Bool DatabaseMetaData::usesLocalFiles( ) +{ + // LEM TODO: + // https://wiki.documentfoundation.org/Documentation/DevGuide/Database_Access#XDatabaseMetaData_Interface + // says "Returns true when the catalog name of the + // database should not appear in the DatasourceBrowser + // of LibreOffice API, otherwise false is returned." + // So, hmmm, think about it. + return false; +} + +sal_Bool DatabaseMetaData::usesLocalFilePerTable( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::supportsMixedCaseIdentifiers( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::storesUpperCaseIdentifiers( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::storesLowerCaseIdentifiers( ) +{ + return true; +} + + +sal_Bool DatabaseMetaData::storesMixedCaseIdentifiers( ) +{ + return false; +} + + +sal_Bool DatabaseMetaData::supportsMixedCaseQuotedIdentifiers( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::storesUpperCaseQuotedIdentifiers( ) +{ + return false; +} + + +sal_Bool DatabaseMetaData::storesLowerCaseQuotedIdentifiers( ) +{ + return false; +} + + +sal_Bool DatabaseMetaData::storesMixedCaseQuotedIdentifiers( ) +{ + return false; +} + + +OUString DatabaseMetaData::getIdentifierQuoteString( ) +{ + return "\""; +} + +OUString DatabaseMetaData::getSQLKeywords( ) +{ + // In Java 6, this is all keywords that are not SQL:2003 + // In Java 2 v1.4 and as per LibreOffice SDK doc, this is all keywords that are not SQL92 + // I understand this to mean "reserved keywords" only. + // See http://www.postgresql.org/docs/current/static/sql-keywords-appendix.html + // LEM TODO: consider using pg_get_keywords(), filter on catcode + return + "ANALYSE," + "ANALYZE," + "ARRAY," //SQL:1999 + "ASYMMETRIC," //SQL:2003 + "BINARY," //SQL:1999 + "CONCURRENTLY," + "CURRENT_CATALOG," //SQL:2008 + "CURRENT_ROLE," //SQL:1999 + "CURRENT_SCHEMA," //SQL:2008 + "DO," + "FREEZE," + "ILIKE," + "ISNULL," + "LIMIT," //SQL:1999; non-reserved in SQL:2003 + "LOCALTIME," //SQL:1999 + "LOCALTIMESTAMP," //SQL:1999 + "NOTNULL," + "OFFSET," //SQL:2008 + "OVER," //SQL:2003 + "PLACING," //non-reserved in SQL:2003 + "RETURNING," //non-reserved in SQL:2008 + "SIMILAR," //SQL:2003 + "VARIADIC," + "VERBOSE," + "WINDOW" //SQL:2003 + ; +} +OUString DatabaseMetaData::getNumericFunctions( ) +{ + // See https://www.postgresql.org/docs/9.1/static/functions-math.html + // LEM TODO: Err... https://wiki.documentfoundation.org/Documentation/DevGuide/Database_Access#Support_Scalar_Functions + // says this should be "Open Group CLI" names, not PostgreSQL names. + // Currently this is just a list of supported functions in PostgreSQL, with PostgreSQL names. + // And it is my job to map from Open Group CLI names/syntax to PostgreSQL names/syntax. Where? By parsing the SQL??? + // Should look at what the JDBC driver is doing. + return + "abs," + "cbrt," + "ceil," + "ceiling," + "degrees," + "div," + "exp," + "floor," + "ln," + "log," + "mod," + "pi," + "power," + "radians," + "random," + "round," + "setseed," + "sign," + "sqrt," + "trunc," + "width_bucket," + "acos," + "asin," + "atan," + "atan2," + "cos," + "cot," + "sin," + "tan" + ; +} + +OUString DatabaseMetaData::getStringFunctions( ) +{ + // See http://www.postgresql.org/docs/9.1/static/functions-string.html + return + "bit_length," + "char_length," + "character_length," + "lower," + "octet_length," + "overlay," + "position," + "substring," + "trim," + "upper," + "ascii," + "btrim," + "chr," + "concat," + "concat_ws," + "convert," + "convert_from," + "convert_to," + "decode," + "encode," + "format," + "initcap," + "left," + "length," + "lpad," + "ltrim," + "md5," + "pg_client_encoding," + "quote_ident," + "quote_literal," + "quote_nullable," + "regexp_matches," + "regexp_replace," + "regexp_split_to_array," + "regexp_split_to_table," + "repeat," + "replace," + "reverse," + "right," + "rpad," + "rtrim," + "split_part," + "strpos," + "substr," + "to_ascii," + "to_hex," + "translate" + ; +} + +OUString DatabaseMetaData::getSystemFunctions( ) +{ + // See http://www.postgresql.org/docs/9.1/static/functions-info.html + // and http://www.postgresql.org/docs/9.1/static/functions-admin.html + return + "current_catalog," + "current_database," + "current_query," + "current_schema," + "current_schemas," + "current_user," + "inet_client_addr," + "inet_client_port," + "inet_server_addr," + "inet_server_port," + "pg_backend_pid," + "pg_conf_load_time," + "pg_is_other_temp_schema," + "pg_listening_channels," + "pg_my_temp_schema," + "pg_postmaster_start_time," + "session_user," + "user," + "version," + "has_any_column_privilege," + "has_any_column_privilege," + "has_any_column_privilege," + "has_column_privilege," + "has_database_privilege," + "has_foreign_data_wrapper_privilege," + "has_function_privilege," + "has_language_privilege," + "has_schema_privilege," + "has_sequence_privilege," + "has_server_privilege," + "has_table_privilege," + "has_tablespace_privilege," + "pg_has_role," + "pg_collation_is_visible," + "pg_conversion_is_visible," + "pg_function_is_visible," + "pg_opclass_is_visible," + "pg_operator_is_visible," + "pg_table_is_visible," + "pg_ts_config_is_visible," + "pg_ts_dict_is_visible," + "pg_ts_parser_is_visible," + "pg_ts_template_is_visible," + "pg_type_is_visible," + "format_type," + "pg_describe_object," + "pg_get_constraintdef," + "pg_get_expr," + "pg_get_functiondef," + "pg_get_function_arguments," + "pg_get_function_identity_arguments," + "pg_get_function_result," + "pg_get_indexdef," + "pg_get_keywords," + "pg_get_ruledef," + "pg_get_serial_sequence," + "pg_get_triggerdef," + "pg_get_userbyid," + "pg_get_viewdef," + "pg_options_to_table," + "pg_tablespace_databases," + "pg_typeof," + "col_description," + "obj_description," + "shobj_description," + "txid_current," + "txid_current_snapshot," + "txid_snapshot_xip," + "txid_snapshot_xmax," + "txid_snapshot_xmin," + "txid_visible_in_snapshot," + "xmin," + "xmax," + "xip_list," + "current_setting," + "set_config," + "pg_cancel_backend," + "pg_reload_conf," + "pg_rotate_logfile," + "pg_terminate_backend," + "pg_create_restore_point," + "pg_current_xlog_insert_location," + "pg_current_xlog_location," + "pg_start_backup," + "pg_stop_backup," + "pg_switch_xlog," + "pg_xlogfile_name," + "pg_xlogfile_name_offset," + "pg_is_in_recovery," + "pg_last_xlog_receive_location," + "pg_last_xlog_replay_location," + "pg_last_xact_replay_timestamp," + "pg_is_xlog_replay_paused," + "pg_xlog_replay_pause," + "pg_xlog_replay_resume," + "pg_column_size," + "pg_database_size," + "pg_indexes_size," + "pg_relation_size," + "pg_size_pretty," + "pg_table_size," + "pg_tablespace_size," + "pg_tablespace_size," + "pg_total_relation_size," + "pg_relation_filenode," + "pg_relation_filepath," + "pg_ls_dir," + "pg_read_file," + "pg_read_binary_file," + "pg_stat_file," + "pg_advisory_lock," + "pg_advisory_lock_shared," + "pg_advisory_unlock," + "pg_advisory_unlock_all," + "pg_advisory_unlock_shared," + "pg_advisory_xact_lock," + "pg_advisory_xact_lock_shared," + "pg_try_advisory_lock," + "pg_try_advisory_lock_shared," + "pg_try_advisory_xact_lock," + "pg_try_advisory_xact_lock_shared," + "pg_sleep" + ; +} +OUString DatabaseMetaData::getTimeDateFunctions( ) +{ + // TODO + return + "age," + "age," + "clock_timestamp," + "current_date," + "current_time," + "current_timestamp," + "date_part," + "date_part," + "date_trunc," + "extract," + "extract," + "isfinite," + "isfinite," + "isfinite," + "justify_days," + "justify_hours," + "justify_interval," + "localtime," + "localtimestamp," + "now," + "statement_timestamp," + "timeofday," + "transaction_timestamp," + ; +} +OUString DatabaseMetaData::getSearchStringEscape( ) +{ + return "\\"; +} +OUString DatabaseMetaData::getExtraNameCharacters( ) +{ + return "$"; +} + +sal_Bool DatabaseMetaData::supportsAlterTableWithAddColumn( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsAlterTableWithDropColumn( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsColumnAliasing( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::nullPlusNonNullIsNull( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsTypeConversion( ) +{ + // LEM: this is specifically whether the "CONVERT" function is supported + // It seems that in PostgreSQL, that function is only for string encoding, so no. + return false; +} + +sal_Bool DatabaseMetaData::supportsConvert( sal_Int32, sal_Int32 ) +{ + return false; +} + +sal_Bool DatabaseMetaData::supportsTableCorrelationNames( ) +{ + // LEM: A correlation name is "bar" in "SELECT foo FROM qux [AS] bar WHERE ..." + return true; +} + + +sal_Bool DatabaseMetaData::supportsDifferentTableCorrelationNames( ) +{ + return false; +} +sal_Bool DatabaseMetaData::supportsExpressionsInOrderBy( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsOrderByUnrelated( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsGroupBy( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsGroupByUnrelated( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsGroupByBeyondSelect( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsLikeEscapeClause( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsMultipleResultSets( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsMultipleTransactions( ) +{ + // Allows multiple transactions open at once (on different connections!) + return true; +} + +sal_Bool DatabaseMetaData::supportsNonNullableColumns( ) +{ + return true; +} + + +sal_Bool DatabaseMetaData::supportsMinimumSQLGrammar( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsCoreSQLGrammar( ) +{ + // LEM: jdbc driver says not, although the comments in it seem old + // fdo#45249 Base query design won't use any aggregate function + // (except COUNT(*) unless we say yes, so say yes. + // Actually, Base assumes *also* support for aggregate functions "collect, fusion, intersection" + // as soon as supportsCoreSQLGrammar() returns true. + // Those are *not* Core SQL, though. They are in optional feature S271 "Basic multiset support" + return true; +} + +sal_Bool DatabaseMetaData::supportsExtendedSQLGrammar( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::supportsANSI92EntryLevelSQL( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsANSI92IntermediateSQL( ) +{ + // LEM: jdbc driver says not, although the comments in it seem old + return false; +} + +sal_Bool DatabaseMetaData::supportsANSI92FullSQL( ) +{ + // LEM: jdbc driver says not, although the comments in it seem old + return false; +} + +sal_Bool DatabaseMetaData::supportsIntegrityEnhancementFacility( ) +{ + // LEM: jdbc driver says yes, although comment says they are not sure what this means... + return true; +} + +sal_Bool DatabaseMetaData::supportsOuterJoins( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsFullOuterJoins( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsLimitedOuterJoins( ) +{ + return true; +} + + +OUString DatabaseMetaData::getSchemaTerm( ) +{ + return "SCHEMA"; +} + +OUString DatabaseMetaData::getProcedureTerm( ) +{ + return "function"; +} + +OUString DatabaseMetaData::getCatalogTerm( ) +{ + return "DATABASE"; +} + +sal_Bool DatabaseMetaData::isCatalogAtStart( ) +{ + return true; +} + +OUString DatabaseMetaData::getCatalogSeparator( ) +{ + return "."; +} + +sal_Bool DatabaseMetaData::supportsSchemasInDataManipulation( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsSchemasInProcedureCalls( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsSchemasInTableDefinitions( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsSchemasInIndexDefinitions( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsSchemasInPrivilegeDefinitions( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsCatalogsInDataManipulation( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::supportsCatalogsInProcedureCalls( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::supportsCatalogsInTableDefinitions( ) +{ + return false; +} + + +sal_Bool DatabaseMetaData::supportsCatalogsInIndexDefinitions( ) +{ + return false; +} + + +sal_Bool DatabaseMetaData::supportsCatalogsInPrivilegeDefinitions( ) +{ + return false; +} + + +// LEM TODO: positioned (through cursor) updates and deletes seem +// to be supported; see {UPDATE,DELETE} /table/ (...) WHERE CURRENT OF /cursor_name/" syntax +// and http://www.postgresql.org/docs/9.1/static/view-pg-cursors.html +// http://www.postgresql.org/docs/9.1/static/libpq-example.html actually uses a cursor :) +sal_Bool DatabaseMetaData::supportsPositionedDelete( ) +{ + // LEM: jdbc driver says not, although the comments in it seem old + return false; +} + +sal_Bool DatabaseMetaData::supportsPositionedUpdate( ) +{ + // LEM: jdbc driver says not, although the comments in it seem old + return false; +} + + +sal_Bool DatabaseMetaData::supportsSelectForUpdate( ) +{ + return true; +} + + +sal_Bool DatabaseMetaData::supportsStoredProcedures( ) +{ + return true; +} + + +sal_Bool DatabaseMetaData::supportsSubqueriesInComparisons( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsSubqueriesInExists( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsSubqueriesInIns( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsSubqueriesInQuantifieds( ) +{ + // LEM: jdbc driver says yes, although comment says they don't know what this means... + return true; +} + +sal_Bool DatabaseMetaData::supportsCorrelatedSubqueries( ) +{ + return true; +} +sal_Bool DatabaseMetaData::supportsUnion( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsUnionAll( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsOpenCursorsAcrossCommit( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::supportsOpenCursorsAcrossRollback( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::supportsOpenStatementsAcrossCommit( ) +{ + return true; +} +sal_Bool DatabaseMetaData::supportsOpenStatementsAcrossRollback( ) +{ + return true; +} + +sal_Int32 DatabaseMetaData::getMaxBinaryLiteralLength( ) +{ + return 0; +} + +sal_Int32 DatabaseMetaData::getMaxCharLiteralLength( ) +{ + return 0; +} + +// Copied / adapted / simplified from JDBC driver +sal_Int32 DatabaseMetaData::getIntSetting(const OUString& settingName) +{ + MutexGuard guard( m_xMutex->GetMutex() ); + + Reference< XParameters > params(m_getIntSetting_stmt, UNO_QUERY_THROW ); + params->setString(1, settingName ); + Reference< XResultSet > rs = m_getIntSetting_stmt->executeQuery(); + Reference< XRow > xRow( rs , UNO_QUERY_THROW ); + OSL_VERIFY(rs->next()); + OSL_ENSURE(rs->isFirst(), "postgresql-sdbc DatabaseMetaData getIntSetting not on first row"); + OSL_ENSURE(rs->isLast(), "postgresql-sdbc DatabaseMetaData getIntSetting not on last row"); + return xRow->getInt(1); +} + +sal_Int32 DatabaseMetaData::getMaxNameLength() +{ + if ( m_pSettings->maxNameLen == 0) + m_pSettings->maxNameLen = getIntSetting( "max_identifier_length" ); + OSL_ENSURE(m_pSettings->maxNameLen, "postgresql-sdbc: maxNameLen is zero"); + return m_pSettings->maxNameLen; +} + +sal_Int32 DatabaseMetaData::getMaxIndexKeys() +{ + if ( m_pSettings->maxIndexKeys == 0) + m_pSettings->maxIndexKeys = getIntSetting("max_index_keys"); + OSL_ENSURE(m_pSettings->maxIndexKeys, "postgresql-sdbc: maxIndexKeys is zero"); + return m_pSettings->maxIndexKeys; +} + +sal_Int32 DatabaseMetaData::getMaxColumnNameLength( ) +{ + return getMaxNameLength(); +} + +sal_Int32 DatabaseMetaData::getMaxColumnsInGroupBy( ) +{ + return 0; +} + +sal_Int32 DatabaseMetaData::getMaxColumnsInIndex( ) +{ + return getMaxIndexKeys(); +} + +sal_Int32 DatabaseMetaData::getMaxColumnsInOrderBy( ) +{ + return 0; +} + +sal_Int32 DatabaseMetaData::getMaxColumnsInSelect( ) +{ + return 0; +} + +sal_Int32 DatabaseMetaData::getMaxColumnsInTable( ) +{ + return 1600; +} + +sal_Int32 DatabaseMetaData::getMaxConnections( ) +{ + // LEM: The JDBC driver returns an arbitrary 8192; truth is as much as OS / hardware supports + return 0; +} + +sal_Int32 DatabaseMetaData::getMaxCursorNameLength( ) //TODO, don't know +{ + return getMaxNameLength(); +} + +sal_Int32 DatabaseMetaData::getMaxIndexLength( ) //TODO, don't know +{ + // LEM: that's the index itself, not its name + return 0; +} + +sal_Int32 DatabaseMetaData::getMaxSchemaNameLength( ) +{ + return getMaxNameLength(); +} + +sal_Int32 DatabaseMetaData::getMaxProcedureNameLength( ) +{ + return getMaxNameLength(); +} + +sal_Int32 DatabaseMetaData::getMaxCatalogNameLength( ) +{ + return getMaxNameLength(); +} + +sal_Int32 DatabaseMetaData::getMaxRowSize( ) +{ + // jdbc driver says 1GB, but http://www.postgresql.org/about/ says 1.6TB + // and that 1GB is the maximum _field_ size + // The row limit does not fit into a sal_Int32 + return 0; +} + +sal_Bool DatabaseMetaData::doesMaxRowSizeIncludeBlobs( ) +{ + // LEM: Err... PostgreSQL basically does not do BLOBs well + // In any case, BLOBs do not change the maximal row length AFAIK + return true; +} + +sal_Int32 DatabaseMetaData::getMaxStatementLength( ) +{ + // LEM: actually, that would be 2^sizeof(size_t)-1 + // on the server? on the client (because of libpq)? minimum of the two? not sure + // Anyway, big, so say unlimited. + return 0; +} + +sal_Int32 DatabaseMetaData::getMaxStatements( ) //TODO, don't know +{ + return 0; +} + +sal_Int32 DatabaseMetaData::getMaxTableNameLength( ) +{ + return getMaxNameLength(); +} + +sal_Int32 DatabaseMetaData::getMaxTablesInSelect( ) +{ + return 0; +} + +sal_Int32 DatabaseMetaData::getMaxUserNameLength( ) +{ + return getMaxNameLength(); +} + +sal_Int32 DatabaseMetaData::getDefaultTransactionIsolation( ) +{ + return css::sdbc::TransactionIsolation::READ_COMMITTED; +} + +sal_Bool DatabaseMetaData::supportsTransactions( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsTransactionIsolationLevel( sal_Int32 level ) +{ + if ( level == css::sdbc::TransactionIsolation::READ_COMMITTED + || level == css::sdbc::TransactionIsolation::SERIALIZABLE + || level == css::sdbc::TransactionIsolation::READ_UNCOMMITTED + || level == css::sdbc::TransactionIsolation::REPEATABLE_READ) + return true; + else + return false; +} + +sal_Bool DatabaseMetaData::supportsDataDefinitionAndDataManipulationTransactions( ) +{ + return true; +} + +sal_Bool DatabaseMetaData::supportsDataManipulationTransactionsOnly( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::dataDefinitionCausesTransactionCommit( ) +{ + return false; +} + +sal_Bool DatabaseMetaData::dataDefinitionIgnoredInTransactions( ) +{ + return false; +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getProcedures( + const css::uno::Any&, + const OUString&, + const OUString& ) +{ +// 1. PROCEDURE_CAT string => procedure catalog (may be NULL ) +// 2. PROCEDURE_SCHEM string => procedure schema (may be NULL ) +// 3. PROCEDURE_NAME string => procedure name +// 4. reserved for future use +// 5. reserved for future use +// 6. reserved for future use +// 7. REMARKS string => explanatory comment on the procedure +// 8. PROCEDURE_TYPE short => kind of procedure: +// * UNKNOWN - May return a result +// * NO - Does not return a result +// * RETURN - Returns a result + +// LEM TODO: implement +// LEM TODO: at least fake the columns, even if no row. + MutexGuard guard( m_xMutex->GetMutex() ); + return new SequenceResultSet( + m_xMutex, *this, std::vector< OUString >(), std::vector< std::vector< Any > > (), m_pSettings->tc ); +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getProcedureColumns( + const css::uno::Any&, + const OUString&, + const OUString&, + const OUString& ) +{ + MutexGuard guard( m_xMutex->GetMutex() ); +// LEM TODO: implement +// LEM TODO: at least fake the columns, even if no row. + return new SequenceResultSet( + m_xMutex, *this, std::vector< OUString >(), std::vector< std::vector< Any > >(), m_pSettings->tc ); +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getTables( + const css::uno::Any&, + const OUString& schemaPattern, + const OUString& tableNamePattern, + const css::uno::Sequence< OUString >& ) +{ + Statics &statics = getStatics(); + + MutexGuard guard( m_xMutex->GetMutex() ); + + SAL_INFO("connectivity.postgresql", "DatabaseMetaData::getTables() got called with " << schemaPattern << "." << tableNamePattern); + + // ignore catalog, as a single pq connection does not support multiple catalogs + + // LEM TODO: this does not give the right column names, not the right number of columns, etc. + // Take "inspiration" from JDBC driver + // Ah, this is used to create a XResultSet manually... Try to do it directly in SQL + Reference< XPreparedStatement > statement = m_origin->prepareStatement( + "SELECT " + "DISTINCT ON (pg_namespace.nspname, relname ) " // avoid duplicates (pg_settings !) + "pg_namespace.nspname, relname, relkind, pg_description.description " + "FROM pg_namespace, pg_class LEFT JOIN pg_description ON pg_class.oid = pg_description.objoid " + "WHERE relnamespace = pg_namespace.oid " + "AND ( relkind = 'r' OR relkind = 'v') " + "AND pg_namespace.nspname LIKE ? " + "AND relname LIKE ? " +// "ORDER BY pg_namespace.nspname || relname" + ); + + Reference< XParameters > parameters( statement, UNO_QUERY_THROW ); + parameters->setString( 1 , schemaPattern ); + parameters->setString( 2 , tableNamePattern ); + + Reference< XResultSet > rs = statement->executeQuery(); + Reference< XRow > xRow( rs, UNO_QUERY_THROW ); + std::vector< std::vector<Any> > vec; + + while( rs->next() ) + { + std::vector< Any > row( 5 ); + + row[0] <<= m_pSettings->catalog; + row[1] <<= xRow->getString( 1 ); + row[2] <<= xRow->getString( 2 ); + OUString type = xRow->getString(3); + if( type == "r" ) + { + if( xRow->getString(1) == "pg_catalog" ) + { + row[3] <<= statics.SYSTEM_TABLE; + } + else + { + row[3] <<= statics.TABLE; + } + } + else if( type == "v" ) + { + row[3] <<= statics.VIEW; + } + else + { + row[3] <<= statics.UNKNOWN; + } + row[4] <<= xRow->getString(4); + + // no description in postgresql AFAIK + vec.push_back( row ); + } + Reference< XCloseable > closeable( statement, UNO_QUERY ); + if( closeable.is() ) + closeable->close(); + + return new SequenceResultSet( + m_xMutex, *this, std::vector(statics.tablesRowNames), std::move(vec), m_pSettings->tc ); +} + +namespace +{ + // sort no schema first, then "public", then normal schemas, then internal schemas + int compare_schema(std::u16string_view nsA, std::u16string_view nsB) + { + if (nsA.empty()) + { + return nsB.empty() ? 0 : -1; + } + else if (nsB.empty()) + { + assert(!nsA.empty()); + return 1; + } + else if(nsA == u"public") + { + return (nsB == u"public") ? 0 : -1; + } + else if(nsB == u"public") + { + assert(nsA != u"public"); + return 1; + } + else if(o3tl::starts_with(nsA, u"pg_")) + { + if(o3tl::starts_with(nsB, u"pg_")) + return nsA.compare(nsB); + else + return 1; + } + else if(o3tl::starts_with(nsB, u"pg_")) + { + return -1; + } + else + { + return nsA.compare(nsB); + } + } + + struct SortInternalSchemasLastAndPublicFirst + { + bool operator () ( const std::vector< Any > & a, const std::vector< Any > & b ) + { + OUString valueA; + OUString valueB; + a[0] >>= valueA; + b[0] >>= valueB; + return compare_schema(valueA, valueB) < 0; + } + }; +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getSchemas( ) +{ + MutexGuard guard( m_xMutex->GetMutex() ); + + SAL_INFO("connectivity.postgresql", "DatabaseMetaData::getSchemas() got called"); + + // <b>TABLE_SCHEM</b> string =&gt; schema name + Reference< XStatement > statement = m_origin->createStatement(); + Reference< XResultSet > rs = statement->executeQuery( + "SELECT nspname from pg_namespace" ); + // LEM TODO: look at JDBC driver and consider doing the same + // in particular, excluding temporary schemas, but maybe better through pg_is_other_temp_schema(oid) OR == pg_my_temp_schema() + + Reference< XRow > xRow( rs, UNO_QUERY_THROW ); + std::vector< std::vector<Any> > vec; + while( rs->next() ) + { + vec.push_back( { Any(xRow->getString(1)) } ); + } + + // sort public first, sort internal schemas last, sort rest in alphabetic order + std::sort( vec.begin(), vec.end(), SortInternalSchemasLastAndPublicFirst() ); + + Reference< XCloseable > closeable( statement, UNO_QUERY ); + if( closeable.is() ) + closeable->close(); + return new SequenceResultSet( + m_xMutex, *this, std::vector(getStatics().schemaNames), std::move(vec), m_pSettings->tc ); +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getCatalogs( ) +{ + // LEM TODO: return the current catalog like JDBC driver? + // at least fake the columns, even if no content + MutexGuard guard( m_xMutex->GetMutex() ); + return new SequenceResultSet( + m_xMutex, *this, std::vector< OUString >(), std::vector< std::vector< Any > >(), m_pSettings->tc ); +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getTableTypes( ) +{ + // LEM TODO: this can be made dynamic, see JDBC driver + MutexGuard guard( m_xMutex->GetMutex() ); + return new SequenceResultSet( + m_xMutex, *this, std::vector(getStatics().tableTypeNames), std::vector(getStatics().tableTypeData), + m_pSettings->tc ); +} + + +/** returns the constant from sdbc.DataType + */ +sal_Int32 typeNameToDataType( const OUString &typeName, std::u16string_view typtype ) +{ +// sal_Int32 ret = css::sdbc::DataType::DISTINCT; + // map all unknown types to memo (longvarchar). This allows to show them in + // string representation. Additionally, the edit-table-type-selection-box + // is not so unusable anymore. + sal_Int32 ret = css::sdbc::DataType::LONGVARCHAR; + if( typtype == u"b" ) + { + // as long as the OOo framework does not support arrays, + // the user is better of with interpreting arrays as strings ! +// if( typeName.getLength() && '_' == typeName[0] ) +// { +// it's just a naming convention, but as long as we don't have anything better, +// we take it as granted +// ret = css::sdbc::DataType::ARRAY; +// } + // base type + Statics &statics = getStatics(); + BaseTypeMap::const_iterator ii = statics.baseTypeMap.find( typeName ); + if( ii != statics.baseTypeMap.end() ) + { + ret = ii->second; + } + } + else if( typtype == u"c" ) + { + ret = css::sdbc::DataType::STRUCT; + } + else if( typtype == u"d" ) + { + ret = css::sdbc::DataType::LONGVARCHAR; + } + return ret; +} + +namespace { + bool isSystemColumn( sal_Int16 attnum ) + { + return attnum <= 0; + } + + // is not exported by the postgres header + const int PQ_VARHDRSZ = sizeof( sal_Int32 ); + + // Oh, quelle horreur + // LEM TODO: Need to severely rewrite that! + // should probably just "do the same" as ODBC or JDBC drivers... + void extractPrecisionAndScale( + sal_Int32 dataType, sal_Int32 atttypmod, sal_Int32 *precision, sal_Int32 *scale ) + { + if( atttypmod < PQ_VARHDRSZ ) + { + *precision = 0; + *scale = 0; + } + else + { + switch( dataType ) + { + case css::sdbc::DataType::NUMERIC: + case css::sdbc::DataType::DECIMAL: + { + *precision = ( ( atttypmod - PQ_VARHDRSZ ) >> 16 ) & 0xffff; + *scale = (atttypmod - PQ_VARHDRSZ ) & 0xffff; + break; + } + default: + *precision = atttypmod - PQ_VARHDRSZ; + *scale = 0; + } + } + } + + struct DatabaseTypeDescription + { + DatabaseTypeDescription() + {} + DatabaseTypeDescription( const OUString &name, const OUString & type ) : + typeName( name ), + typeType( type ) + {} + DatabaseTypeDescription( const DatabaseTypeDescription &source ) : + typeName( source.typeName ), + typeType( source.typeType ) + {} + DatabaseTypeDescription & operator = ( const DatabaseTypeDescription & source ) + { + typeName = source.typeName; + typeType = source.typeType; + return *this; + } + OUString typeName; + OUString typeType; + }; +} + +typedef std::unordered_map +< + sal_Int32, + DatabaseTypeDescription +> Oid2DatabaseTypeDescriptionMap; + +static void columnMetaData2DatabaseTypeDescription( + Oid2DatabaseTypeDescriptionMap &oidMap, + const Reference< XResultSet > &rs, + const Reference< XStatement > &stmt ) +{ + Reference< XRow > row( rs, UNO_QUERY_THROW ); + int domains = 0; + OUStringBuffer queryBuf(128); + queryBuf.append( "SELECT oid,typtype,typname FROM pg_TYPE WHERE " ); + while( rs->next() ) + { + if( row->getString( 9 ) == "d" && oidMap.find( row->getInt( 12 ) ) == oidMap.end() ) + { + oidMap[row->getInt(12)] = DatabaseTypeDescription(); + if( domains ) + queryBuf.append( " OR " ); + queryBuf.append( "oid = " ); + queryBuf.append( row->getInt(12 ) ); + domains ++; + } + } + rs->beforeFirst(); + + if( domains ) + { + Reference< XResultSet > rsDomain = stmt->executeQuery( queryBuf.makeStringAndClear() ); + row.set( rsDomain, UNO_QUERY_THROW ); + while( rsDomain->next() ) + { + oidMap[row->getInt(1)] = DatabaseTypeDescription(row->getString(3), row->getString(2) ); + } + disposeNoThrow( stmt ); + } + +} + + +css::uno::Reference< XResultSet > DatabaseMetaData::getColumns( + const css::uno::Any&, + const OUString& schemaPattern, + const OUString& tableNamePattern, + const OUString& columnNamePattern ) +{ + // LEM TODO: review in comparison with JDBC driver + Statics &statics = getStatics(); + + // continue ! + MutexGuard guard( m_xMutex->GetMutex() ); + + SAL_INFO("connectivity.postgresql", "DatabaseMetaData::getColumns() got called with " + << schemaPattern << "." << tableNamePattern << "." << columnNamePattern); + + // ignore catalog, as a single pq connection + // does not support multiple catalogs anyway + // We don't use information_schema.columns because it contains + // only the columns the current user has any privilege over. + + // 1. TABLE_CAT string => table catalog (may be NULL) + // => not supported + // 2. TABLE_SCHEM string => table schema (may be NULL) + // => pg_namespace.nspname + // 3. TABLE_NAME string => table name + // => pg_class.relname + // 4. COLUMN_NAME string => column name + // => pg_attribute.attname + // 5. DATA_TYPE short => SQL type from java.sql.Types + // => pg_type.typname => sdbc.DataType + // 6. TYPE_NAME string => Data source dependent type name, for a UDT the + // type name is fully qualified + // => pg_type.typname + // 7. COLUMN_SIZE long => column size. For char or date types this is + // the maximum number of characters, for numeric + // or decimal types this is precision. + // => pg_attribute.atttypmod + // 8. BUFFER_LENGTH is not used. + // => not used + // 9. DECIMAL_DIGITS long => the number of fractional digits + // => don't know ! TODO ! + // 10. NUM_PREC_RADIX long => Radix (typically either 10 or 2) + // => TODO ?? + // 11. NULLABLE long => is NULL allowed? + // NO_NULLS - might not allow NULL values + // NULABLE - definitely allows NULL values + // NULLABLE_UNKNOWN - nullability unknown + // => pg_attribute.attnotnull + // 12. REMARKS string => comment describing column (may be NULL ) + // => pg_description.description + // 13. COLUMN_DEF string => default value (may be NULL) + // => pg_type.typdefault + // 14. SQL_DATA_TYPE long => unused + // => empty + // 15. SQL_DATETIME_SUB long => unused + // => empty + // 16. CHAR_OCTET_LENGTH long => for char types the maximum number of + // bytes in the column + // => pg_type.typlen + // 17. ORDINAL_POSITION int => index of column in table (starting at 1) + // pg_attribute.attnum + // 18. IS_NULLABLE string => "NO" means column definitely does not allow + // NULL values; "YES" means the column might + // allow NULL values. An empty string means + // nobody knows. + // => pg_attribute.attnotnull + OUString strDefaultValue = getColExprForDefaultSettingVal(m_pSettings); + Reference< XPreparedStatement > statement = m_origin->prepareStatement( + "SELECT pg_namespace.nspname, " // 1 + "pg_class.relname, " // 2 + "pg_attribute.attname, " // 3 + "pg_type.typname, " // 4 + "pg_attribute.atttypmod, " // 5 + "pg_attribute.attnotnull, " // 6 + "pg_type.typdefault, " // 7 + "pg_type.typtype, " // 8 + + strDefaultValue + // 9 + ",pg_description.description, " // 10 + "pg_type.typbasetype, " // 11 + "pg_attribute.attnum " // 12 + "FROM pg_class, " + "pg_attribute LEFT JOIN pg_attrdef ON pg_attribute.attrelid = pg_attrdef.adrelid AND pg_attribute.attnum = pg_attrdef.adnum " + "LEFT JOIN pg_description ON pg_attribute.attrelid = pg_description.objoid AND pg_attribute.attnum=pg_description.objsubid," + " pg_type, pg_namespace " + "WHERE pg_attribute.attrelid = pg_class.oid " + "AND pg_attribute.atttypid = pg_type.oid " + "AND pg_class.relnamespace = pg_namespace.oid " + "AND NOT pg_attribute.attisdropped " + "AND pg_namespace.nspname LIKE ? " + "AND pg_class.relname LIKE ? " + "AND pg_attribute.attname LIKE ? " + "ORDER BY pg_namespace.nspname, pg_class.relname, pg_attribute.attnum" + ); + + Reference< XParameters > parameters( statement, UNO_QUERY_THROW ); + parameters->setString( 1 , schemaPattern ); + parameters->setString( 2 , tableNamePattern ); + parameters->setString( 3 , columnNamePattern ); + + Reference< XResultSet > rs = statement->executeQuery(); + Reference< XRow > xRow( rs, UNO_QUERY_THROW ); + std::vector< std::vector<Any> > vec; + + Oid2DatabaseTypeDescriptionMap domainMap; + Reference< XStatement > domainTypeStmt = m_origin->createStatement(); + columnMetaData2DatabaseTypeDescription( domainMap, rs, domainTypeStmt ); + + sal_uInt32 colNum(0); + OUString sSchema( "#invalid#" ); + OUString sTable( "#invalid#" ); + + while( rs->next() ) + { + if( ! isSystemColumn( xRow->getShort( 12 ) ) ) + { + OUString sNewSchema( xRow->getString(1) ); + OUString sNewTable( xRow->getString(2) ); + if ( sNewSchema != sSchema || sNewTable != sTable ) + { + colNum = 1; + sSchema = sNewSchema; + sTable = sNewTable; + } + else + ++colNum; + sal_Int32 precision, scale, type; + std::vector< Any > row( 18 ); + row[0] <<= m_pSettings->catalog; + row[1] <<= sNewSchema; + row[2] <<= sNewTable; + row[3] <<= xRow->getString(3); + if( xRow->getString(8) == "d" ) + { + DatabaseTypeDescription desc( domainMap[xRow->getInt(11)] ); + type = typeNameToDataType( desc.typeName, desc.typeType ); + } + else + { + type = typeNameToDataType( xRow->getString(4), xRow->getString(8) ); + } + extractPrecisionAndScale( type, xRow->getInt(5) , &precision, &scale ); + row[4] <<= type; + row[5] <<= xRow->getString(4); + row[6] <<= precision; + // row[7] BUFFER_LENGTH not used + row[8] <<= scale; + // row[9] RADIX TODO + if( xRow->getBoolean( 6 ) && ! isSystemColumn(xRow->getInt( 12 )) ) + { + row[10] <<= OUString::number(css::sdbc::ColumnValue::NO_NULLS); + row[17] <<= statics.NO; + } + else + { + row[10] <<= OUString::number(css::sdbc::ColumnValue::NULLABLE); + row[17] <<= statics.YES; + } + + row[11] <<= xRow->getString( 10 ); // comment + row[12] <<= xRow->getString( 9 ); // COLUMN_DEF = pg_type.typdefault + // row[13] SQL_DATA_TYPE not used + // row[14] SQL_DATETIME_SUB not used + row[15] <<= precision; + row[16] <<= colNum ; + + vec.push_back( row ); + } + } + Reference< XCloseable > closeable( statement, UNO_QUERY ); + if( closeable.is() ) + closeable->close(); + + return new SequenceResultSet( + m_xMutex, *this, std::vector(statics.columnRowNames), std::move(vec), m_pSettings->tc ); +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getColumnPrivileges( + const css::uno::Any&, + const OUString& schema, + const OUString& table, + const OUString& columnNamePattern ) +{ + MutexGuard guard( m_xMutex->GetMutex() ); + + SAL_INFO("connectivity.postgresql", "DatabaseMetaData::getColumnPrivileges() got called with " + << schema << "." << table << "." << columnNamePattern); + + Reference< XParameters > parameters( m_getColumnPrivs_stmt, UNO_QUERY_THROW ); + parameters->setString( 1 , schema ); + parameters->setString( 2 , table ); + parameters->setString( 3 , columnNamePattern ); + + Reference< XResultSet > rs = m_getColumnPrivs_stmt->executeQuery(); + + return rs; +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getTablePrivileges( + const css::uno::Any&, + const OUString& schemaPattern, + const OUString& tableNamePattern ) +{ + MutexGuard guard( m_xMutex->GetMutex() ); + + SAL_INFO("connectivity.postgresql", "DatabaseMetaData::getTablePrivileges() got called with " + << schemaPattern << "." << tableNamePattern); + + Reference< XParameters > parameters( m_getTablePrivs_stmt, UNO_QUERY_THROW ); + parameters->setString( 1 , schemaPattern ); + parameters->setString( 2 , tableNamePattern ); + + Reference< XResultSet > rs = m_getTablePrivs_stmt->executeQuery(); + + return rs; +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getBestRowIdentifier( + const css::uno::Any&, + const OUString&, + const OUString&, + sal_Int32, + sal_Bool ) +{ + //LEM TODO: implement! See JDBC driver + MutexGuard guard( m_xMutex->GetMutex() ); + return new SequenceResultSet( + m_xMutex, *this, std::vector< OUString >(), std::vector< std::vector< Any > >(), m_pSettings->tc ); +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getVersionColumns( + const css::uno::Any&, + const OUString&, + const OUString& ) +{ + //LEM TODO: implement! See JDBC driver + MutexGuard guard( m_xMutex->GetMutex() ); + return new SequenceResultSet( + m_xMutex, *this, std::vector< OUString >(), std::vector< std::vector< Any > >(), m_pSettings->tc ); +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getPrimaryKeys( + const css::uno::Any&, + const OUString& schema, + const OUString& table ) +{ + //LEM TODO: review + MutexGuard guard( m_xMutex->GetMutex() ); + +// 1. TABLE_CAT string => table catalog (may be NULL ) +// 2. TABLE_SCHEM string => table schema (may be NULL ) +// 3. TABLE_NAME string => table name +// 4. COLUMN_NAME string => column name +// 5. KEY_SEQ short => sequence number within primary key +// 6. PK_NAME string => primary key name (may be NULL ) + + SAL_INFO("connectivity.postgresql", "DatabaseMetaData::getPrimaryKeys() got called with " + << schema << "." << table); + + Reference< XPreparedStatement > statement = m_origin->prepareStatement( + "SELECT nmsp.nspname, " + "cl.relname, " + "con.conkey, " + "con.conname, " + "con.conrelid " + "FROM pg_constraint as con,pg_class as cl, pg_namespace as nmsp " + "WHERE con.connamespace = nmsp.oid AND con.conrelid = cl.oid AND con.contype = 'p' " + "AND nmsp.nspname LIKE ? AND cl.relname LIKE ?" ); + + Reference< XParameters > parameters( statement, UNO_QUERY_THROW ); + parameters->setString( 1 , schema ); + parameters->setString( 2 , table ); + + Reference< XResultSet > rs = statement->executeQuery(); + Reference< XRow > xRow( rs, UNO_QUERY_THROW ); + std::vector< std::vector<Any> > vec; + + while( rs->next() ) + { + std::vector< Any > row( 6 ); + row[0] <<= m_pSettings->catalog; + row[1] <<= xRow->getString(1); + row[2] <<= xRow->getString(2); + OUString array = xRow->getString(3); + row[4] <<= xRow->getString(5); // the relid + row[5] <<= xRow->getString(4); + + int i = 0; + // now retrieve the columns information + // unfortunately, postgresql does not allow array of variable size in + // WHERE clauses (in the default installation), so we have to choose + // this expensive and somewhat ugly way + // annotation: postgresql shouldn't have chosen an array here, instead they + // should have multiple rows per table + // LEM: to transform an array into several rows, see unnest; + // it is as simple as "SELECT foo, bar, unnest(qux) FROM ..." + // where qux is the column that contains an array. + while( array[i] && '}' != array[i] ) + { + i++; + int start = i; + while( array[i] && array[i] != '}' && array[i] != ',' ) i++; + row[3] <<= array.copy(start, i - start ); + vec.push_back( row ); + } + } + + { + Reference< XCloseable > closeable( statement, UNO_QUERY ); + if( closeable.is() ) + closeable->close(); + } + + + OUString lastTableOid; + sal_Int32 index = 0; + std::vector< std::vector< Any > > ret( vec.size() ); + int elements = 0; + for (auto const& elem : vec) + { + + std::vector< Any > row = elem; + OUString tableOid; + OUString attnum; + + row[4] >>= tableOid; + row[3] >>= attnum; + statement = m_origin->prepareStatement( + "SELECT att.attname FROM " + "pg_attribute AS att, pg_class AS cl WHERE " + "att.attrelid = ? AND att.attnum = ?" ); + + parameters.set( statement, UNO_QUERY_THROW ); + parameters->setString( 1 , tableOid ); + parameters->setString( 2 , attnum ); + + rs = statement->executeQuery(); + xRow.set( rs, UNO_QUERY_THROW ); + if( rs->next() ) + { + // column name + row[3] <<= xRow->getString( 1 ); + if( tableOid != lastTableOid ) + index = 1; + lastTableOid = tableOid; + row[4] <<= OUString::number( index ); + index ++; + } + { + Reference< XCloseable > closeable( statement, UNO_QUERY ); + if( closeable.is() ) + closeable->close(); + } + ret[elements] = row; + elements ++; + } + return new SequenceResultSet( + m_xMutex, *this, std::vector(getStatics().primaryKeyNames), std::move(ret), m_pSettings->tc ); +} + +// Copied / adapted / simplified from JDBC driver +#define SQL_CASE_KEYRULE " WHEN 'c' THEN " SAL_STRINGIFY(KEYRULE_CASCADE) \ + " WHEN 'n' THEN " SAL_STRINGIFY(KEYRULE_SET_NULL) \ + " WHEN 'd' THEN " SAL_STRINGIFY(KEYRULE_SET_DEFAULT) \ + " WHEN 'r' THEN " SAL_STRINGIFY(KEYRULE_RESTRICT) \ + " WHEN 'a' THEN " SAL_STRINGIFY(KEYRULE_NO_ACTION) \ + " ELSE NULL " + +#define SQL_GET_REFERENCES \ + "WITH con AS (SELECT oid, conname, contype, condeferrable, condeferred, conrelid, confrelid, confupdtype, confdeltype, generate_subscripts(conkey,1) AS conkeyseq, unnest(conkey) AS conkey , unnest(confkey) AS confkey FROM pg_catalog.pg_constraint) " \ + "SELECT NULL::text AS PKTABLE_CAT, pkn.nspname AS PKTABLE_SCHEM, pkc.relname AS PKTABLE_NAME, pka.attname AS PKCOLUMN_NAME, " \ + " NULL::text AS FKTABLE_CAT, fkn.nspname AS FKTABLE_SCHEM, fkc.relname AS FKTABLE_NAME, fka.attname AS FKCOLUMN_NAME, " \ + " con.conkeyseq AS KEY_SEQ, " \ + " CASE con.confupdtype " \ + SQL_CASE_KEYRULE \ + " END AS UPDATE_RULE, " \ + " CASE con.confdeltype " \ + SQL_CASE_KEYRULE \ + " END AS DELETE_RULE, " \ + " con.conname AS FK_NAME, pkic.relname AS PK_NAME, " \ + " CASE " \ + " WHEN con.condeferrable AND con.condeferred THEN " SAL_STRINGIFY(DEFERRABILITY_INITIALLY_DEFERRED) \ + " WHEN con.condeferrable THEN " SAL_STRINGIFY(DEFERRABILITY_INITIALLY_IMMEDIATE) \ + " ELSE " SAL_STRINGIFY(DEFERRABILITY_NONE) \ + " END AS DEFERRABILITY " \ + "FROM " \ + " pg_catalog.pg_namespace pkn, pg_catalog.pg_class pkc, pg_catalog.pg_attribute pka, " \ + " pg_catalog.pg_namespace fkn, pg_catalog.pg_class fkc, pg_catalog.pg_attribute fka, " \ + " con, pg_catalog.pg_depend dep, pg_catalog.pg_class pkic " \ + "WHERE pkn.oid = pkc.relnamespace AND pkc.oid = pka.attrelid AND pka.attnum = con.confkey AND con.confrelid = pkc.oid " \ + " AND fkn.oid = fkc.relnamespace AND fkc.oid = fka.attrelid AND fka.attnum = con.conkey AND con.conrelid = fkc.oid " \ + " AND con.contype = 'f' AND con.oid = dep.objid AND pkic.oid = dep.refobjid AND pkic.relkind = 'i' AND dep.classid = 'pg_constraint'::regclass::oid AND dep.refclassid = 'pg_class'::regclass::oid " + +#define SQL_GET_REFERENCES_PSCHEMA " AND pkn.nspname = ? " +#define SQL_GET_REFERENCES_PTABLE " AND pkc.relname = ? " +#define SQL_GET_REFERENCES_FSCHEMA " AND fkn.nspname = ? " +#define SQL_GET_REFERENCES_FTABLE " AND fkc.relname = ? " +#define SQL_GET_REFERENCES_ORDER_SOME_PTABLE "ORDER BY fkn.nspname, fkc.relname, conkeyseq" +#define SQL_GET_REFERENCES_ORDER_NO_PTABLE "ORDER BY pkn.nspname, pkc.relname, conkeyseq" + +#define SQL_GET_REFERENCES_NONE_NONE_NONE_NONE \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_ORDER_NO_PTABLE + +#define SQL_GET_REFERENCES_SOME_NONE_NONE_NONE \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PSCHEMA \ + SQL_GET_REFERENCES_ORDER_NO_PTABLE + +#define SQL_GET_REFERENCES_NONE_SOME_NONE_NONE \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PTABLE \ + SQL_GET_REFERENCES_ORDER_SOME_PTABLE + +#define SQL_GET_REFERENCES_SOME_SOME_NONE_NONE \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PSCHEMA \ + SQL_GET_REFERENCES_PTABLE \ + SQL_GET_REFERENCES_ORDER_SOME_PTABLE + +#define SQL_GET_REFERENCES_NONE_NONE_SOME_NONE \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_FSCHEMA \ + SQL_GET_REFERENCES_ORDER_NO_PTABLE + +#define SQL_GET_REFERENCES_NONE_NONE_NONE_SOME \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_FTABLE \ + SQL_GET_REFERENCES_ORDER_NO_PTABLE + +#define SQL_GET_REFERENCES_NONE_NONE_SOME_SOME \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_FSCHEMA \ + SQL_GET_REFERENCES_FTABLE \ + SQL_GET_REFERENCES_ORDER_NO_PTABLE + +#define SQL_GET_REFERENCES_SOME_NONE_SOME_NONE \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PSCHEMA \ + SQL_GET_REFERENCES_FSCHEMA \ + SQL_GET_REFERENCES_ORDER_NO_PTABLE + +#define SQL_GET_REFERENCES_SOME_NONE_NONE_SOME \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PSCHEMA \ + SQL_GET_REFERENCES_FTABLE \ + SQL_GET_REFERENCES_ORDER_NO_PTABLE + +#define SQL_GET_REFERENCES_SOME_NONE_SOME_SOME \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PSCHEMA \ + SQL_GET_REFERENCES_FSCHEMA \ + SQL_GET_REFERENCES_FTABLE \ + SQL_GET_REFERENCES_ORDER_NO_PTABLE + +#define SQL_GET_REFERENCES_NONE_SOME_SOME_NONE \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PTABLE \ + SQL_GET_REFERENCES_FSCHEMA \ + SQL_GET_REFERENCES_ORDER_SOME_PTABLE + +#define SQL_GET_REFERENCES_NONE_SOME_NONE_SOME \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PTABLE \ + SQL_GET_REFERENCES_FTABLE \ + SQL_GET_REFERENCES_ORDER_SOME_PTABLE + +#define SQL_GET_REFERENCES_NONE_SOME_SOME_SOME \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PTABLE \ + SQL_GET_REFERENCES_FSCHEMA \ + SQL_GET_REFERENCES_FTABLE \ + SQL_GET_REFERENCES_ORDER_SOME_PTABLE + +#define SQL_GET_REFERENCES_SOME_SOME_SOME_NONE \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PSCHEMA \ + SQL_GET_REFERENCES_PTABLE \ + SQL_GET_REFERENCES_FSCHEMA \ + SQL_GET_REFERENCES_ORDER_SOME_PTABLE + +#define SQL_GET_REFERENCES_SOME_SOME_NONE_SOME \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PSCHEMA \ + SQL_GET_REFERENCES_PTABLE \ + SQL_GET_REFERENCES_FTABLE \ + SQL_GET_REFERENCES_ORDER_SOME_PTABLE + +#define SQL_GET_REFERENCES_SOME_SOME_SOME_SOME \ + SQL_GET_REFERENCES \ + SQL_GET_REFERENCES_PSCHEMA \ + SQL_GET_REFERENCES_PTABLE \ + SQL_GET_REFERENCES_FSCHEMA \ + SQL_GET_REFERENCES_FTABLE \ + SQL_GET_REFERENCES_ORDER_SOME_PTABLE + +void DatabaseMetaData::init_getReferences_stmt () +{ + m_getReferences_stmt[0] = m_origin->prepareStatement(SQL_GET_REFERENCES_NONE_NONE_NONE_NONE); + m_getReferences_stmt[1] = m_origin->prepareStatement(SQL_GET_REFERENCES_SOME_NONE_NONE_NONE); + m_getReferences_stmt[2] = m_origin->prepareStatement(SQL_GET_REFERENCES_NONE_SOME_NONE_NONE); + m_getReferences_stmt[3] = m_origin->prepareStatement(SQL_GET_REFERENCES_SOME_SOME_NONE_NONE); + m_getReferences_stmt[4] = m_origin->prepareStatement(SQL_GET_REFERENCES_NONE_NONE_SOME_NONE); + m_getReferences_stmt[5] = m_origin->prepareStatement(SQL_GET_REFERENCES_SOME_NONE_SOME_NONE); + m_getReferences_stmt[6] = m_origin->prepareStatement(SQL_GET_REFERENCES_NONE_SOME_SOME_NONE); + m_getReferences_stmt[7] = m_origin->prepareStatement(SQL_GET_REFERENCES_SOME_SOME_SOME_NONE); + m_getReferences_stmt[8] = m_origin->prepareStatement(SQL_GET_REFERENCES_NONE_NONE_NONE_SOME); + m_getReferences_stmt[9] = m_origin->prepareStatement(SQL_GET_REFERENCES_SOME_NONE_NONE_SOME); + m_getReferences_stmt[10] = m_origin->prepareStatement(SQL_GET_REFERENCES_NONE_SOME_NONE_SOME); + m_getReferences_stmt[11] = m_origin->prepareStatement(SQL_GET_REFERENCES_SOME_SOME_NONE_SOME); + m_getReferences_stmt[12] = m_origin->prepareStatement(SQL_GET_REFERENCES_NONE_NONE_SOME_SOME); + m_getReferences_stmt[13] = m_origin->prepareStatement(SQL_GET_REFERENCES_SOME_NONE_SOME_SOME); + m_getReferences_stmt[14] = m_origin->prepareStatement(SQL_GET_REFERENCES_NONE_SOME_SOME_SOME); + m_getReferences_stmt[15] = m_origin->prepareStatement(SQL_GET_REFERENCES_SOME_SOME_SOME_SOME); +} + +void DatabaseMetaData::init_getPrivs_stmt () +{ + OUStringBuffer sSQL(300); + sSQL.append( + " SELECT dp.TABLE_CAT, dp.TABLE_SCHEM, dp.TABLE_NAME, dp.GRANTOR, pr.rolname AS GRANTEE, dp.privilege, dp.is_grantable " + " FROM (" + " SELECT table_catalog AS TABLE_CAT, table_schema AS TABLE_SCHEM, table_name," + " grantor, grantee, privilege_type AS PRIVILEGE, is_grantable" + " FROM information_schema.table_privileges"); + if ( PQserverVersion( m_pSettings->pConnection ) < 90200 ) + // information_schema.table_privileges does not fill in default ACLs when no ACL + // assume default ACL is "owner has all privileges" and add it + sSQL.append( + " UNION " + " SELECT current_database() AS TABLE_CAT, pn.nspname AS TABLE_SCHEM, c.relname AS TABLE_NAME," + " ro.rolname AS GRANTOR, rg.rolname AS GRANTEE, p.privilege, 'YES' AS is_grantable" + " FROM pg_catalog.pg_class c," + " (VALUES ('SELECT'), ('INSERT'), ('UPDATE'), ('DELETE'), ('TRUNCATE'), ('REFERENCES'), ('TRIGGER')) p (privilege)," + " pg_catalog.pg_roles ro," + " ( SELECT oid, rolname FROM pg_catalog.pg_roles" + " UNION ALL" + " VALUES (0::oid, 'PUBLIC')" + " ) AS rg (oid, rolname)," + " pg_catalog.pg_namespace pn" + " WHERE c.relkind IN ('r', 'v') AND c.relacl IS NULL AND pg_has_role(rg.oid, c.relowner, 'USAGE')" + " AND c.relowner=ro.oid AND c.relnamespace = pn.oid"); + sSQL.append( + " ) dp," + " (SELECT oid, rolname FROM pg_catalog.pg_roles UNION ALL VALUES (0, 'PUBLIC')) pr" + " WHERE table_schem LIKE ? AND table_name LIKE ? AND (dp.grantee = 'PUBLIC' OR pg_has_role(pr.oid, dp.grantee, 'USAGE'))" + " ORDER BY table_schem, table_name, privilege" ); + + m_getTablePrivs_stmt = m_origin->prepareStatement( sSQL.makeStringAndClear() ); + + sSQL.append( + " SELECT dp.TABLE_CAT, dp.TABLE_SCHEM, dp.TABLE_NAME, dp.COLUMN_NAME, dp.GRANTOR, pr.rolname AS GRANTEE, dp.PRIVILEGE, dp.IS_GRANTABLE FROM (" + " SELECT table_catalog AS TABLE_CAT, table_schema AS TABLE_SCHEM, table_name, column_name," + " grantor, grantee, privilege_type AS PRIVILEGE, is_grantable" + " FROM information_schema.column_privileges"); + if ( PQserverVersion( m_pSettings->pConnection ) < 90200 ) + // information_schema.table_privileges does not fill in default ACLs when no ACL + // assume default ACL is "owner has all privileges" and add it + sSQL.append( + " UNION " + " SELECT current_database() AS TABLE_CAT, pn.nspname AS TABLE_SCHEM, c.relname AS TABLE_NAME, a.attname AS column_name," + " ro.rolname AS GRANTOR, rg.rolname AS GRANTEE, p.privilege, 'YES' AS is_grantable" + " FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a," + " (VALUES ('SELECT'), ('INSERT'), ('UPDATE'), ('REFERENCES')) p (privilege)," + " pg_catalog.pg_roles ro," + " ( SELECT oid, rolname FROM pg_catalog.pg_roles" + " UNION ALL" + " VALUES (0::oid, 'PUBLIC')" + " ) AS rg (oid, rolname)," + " pg_catalog.pg_namespace pn" + " WHERE c.relkind IN ('r', 'v') AND c.relacl IS NULL AND pg_has_role(rg.oid, c.relowner, 'USAGE')" + " AND c.relowner=ro.oid AND c.relnamespace = pn.oid AND a.attrelid = c.oid AND a.attnum > 0"); + sSQL.append( + " ) dp," + " (SELECT oid, rolname FROM pg_catalog.pg_roles UNION ALL VALUES (0, 'PUBLIC')) pr" + " WHERE table_schem = ? AND table_name = ? AND column_name LIKE ? AND (dp.grantee = 'PUBLIC' OR pg_has_role(pr.oid, dp.grantee, 'USAGE'))" + " ORDER BY column_name, privilege" ); + + m_getColumnPrivs_stmt = m_origin->prepareStatement( sSQL.makeStringAndClear() ); +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getImportedExportedKeys( + const Any& /* primaryCatalog */, + const OUString& primarySchema, + const OUString& primaryTable, + const Any& /* foreignCatalog */, + const OUString& foreignSchema, + const OUString& foreignTable ) +{ + unsigned int i = 0; + if ( ! primarySchema.isEmpty() ) + i |= 0x01; + if ( ! primaryTable.isEmpty() ) + i |= 0x02; + if ( ! foreignSchema.isEmpty() ) + i |= 0x04; + if ( ! foreignTable.isEmpty() ) + i |= 0x08; + + Reference< XPreparedStatement > stmt = m_getReferences_stmt[i]; + Reference< XParameters > param ( stmt, UNO_QUERY_THROW ); + + unsigned int j = 1; + if ( i & 0x01 ) + param->setString( j++, primarySchema ); + if ( i & 0x02 ) + param->setString( j++, primaryTable ); + if ( i & 0x04 ) + param->setString( j++, foreignSchema ); + if ( i & 0x08 ) + param->setString( j++, foreignTable ); + + Reference< XResultSet > rs = stmt->executeQuery(); + + return rs; +} + + +css::uno::Reference< XResultSet > DatabaseMetaData::getImportedKeys( + const css::uno::Any& catalog, + const OUString& schema, + const OUString& table ) +{ + return getImportedExportedKeys(Any(), OUString(), OUString(), catalog, schema, table); +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getExportedKeys( + const css::uno::Any& catalog, + const OUString& schema, + const OUString& table ) +{ + return getImportedExportedKeys(catalog, schema, table, Any(), OUString(), OUString()); +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getCrossReference( + const css::uno::Any& primaryCatalog, + const OUString& primarySchema, + const OUString& primaryTable, + const css::uno::Any& foreignCatalog, + const OUString& foreignSchema, + const OUString& foreignTable ) +{ + return getImportedExportedKeys( primaryCatalog, primarySchema, primaryTable, foreignCatalog, foreignSchema, foreignTable ); +} + +namespace +{ + struct TypeInfoByDataTypeSorter + { + bool operator () ( const std::vector< Any > & a, const std::vector< Any > & b ) + { + OUString valueA; + OUString valueB; + a[1 /*DATA_TYPE*/] >>= valueA; + b[1 /*DATA_TYPE*/] >>= valueB; + if( valueB.toInt32() == valueA.toInt32() ) + { + OUString nameA; + OUString nameB; + a[0 /*TYPE_NAME*/] >>= nameA; + b[0 /*TYPE_NAME*/] >>= nameB; + std::u16string_view nsA, tnA, nsB, tnB; + + // parse typename into schema and typename + sal_Int32 nIndex=0; + nsA = o3tl::getToken(nameA, 0, '.', nIndex); + if (nIndex<0) + { + tnA = nsA; + nsA = std::u16string_view(); + } + else + { + tnA = o3tl::getToken(nameA, 0, '.', nIndex); + assert(nIndex < 0); + } + + nIndex=0; + nsB = o3tl::getToken(nameB, 0, '.', nIndex); + if (nIndex<0) + { + tnB = nsB; + nsB = std::u16string_view(); + } + else + { + tnB = o3tl::getToken(nameB, 0, '.', nIndex); + assert(nIndex < 0); + } + + const int ns_comp = compare_schema(nsA, nsB); + if(ns_comp == 0) + { + if(nsA.empty()) + { + assert(nsB.empty()); + // within each type category, sort privileged choice first + if( tnA == u"int4" || tnA == u"varchar" || tnA == u"char" || tnA == u"text") + return true; + if( tnB == u"int4" || tnB == u"varchar" || tnB == u"char" || tnB == u"text") + return false; + } + return nameA.compareTo( nameB ) < 0; + } + else + { + return ns_comp < 0; + } + } + + return valueA.toInt32() < valueB.toInt32(); + } + }; + + sal_Int32 calcSearchable( sal_Int32 dataType ) + { + sal_Int32 ret = css::sdbc::ColumnSearch::FULL; + if( css::sdbc::DataType::BINARY == dataType || + css::sdbc::DataType::VARBINARY == dataType || + css::sdbc::DataType::LONGVARBINARY == dataType ) + ret = css::sdbc::ColumnSearch::NONE; + + return ret; + } + + sal_Int32 getMaxScale( sal_Int32 dataType ) + { + // LEM TODO: review, see where used, see JDBC, ... + sal_Int32 ret = 0; + if( dataType == css::sdbc::DataType::NUMERIC ) + ret = 1000; // see pg-docs DataType/numeric +// else if( dataType == DataType::DOUBLE ) +// ret = 308; +// else if( dataType == DataType::FLOAT ) +// ret = + return ret; + } + + OUString construct_full_typename(std::u16string_view ns, const OUString &tn) + { + if(ns.empty() || ns == u"pg_catalog") + return tn; + else + return OUString::Concat(ns) + "." + tn; + } + + void pgTypeInfo2ResultSet( + std::vector< std::vector<Any> > &vec, + const Reference< XResultSet > &rs ) + { + static const sal_Int32 TYPE_NAME = 0; // string Type name + static const sal_Int32 DATA_TYPE = 1; // short SQL data type from java.sql.Types + static const sal_Int32 PRECISION = 2; // long maximum precision + static const sal_Int32 CREATE_PARAMS = 5; // string => parameters used in creating the type (may be NULL ) + static const sal_Int32 NULLABLE = 6; // short ==> can you use NULL for this type? + // - NO_NULLS - does not allow NULL values + // - NULLABLE - allows NULL values + // - NULLABLE_UNKNOWN - nullability unknown + + static const sal_Int32 CASE_SENSITIVE = 7; // boolean==> is it case sensitive + static const sal_Int32 SEARCHABLE = 8; // short ==>; can you use + // "WHERE" based on this type: + // - NONE - No support + // - CHAR - Only supported with WHERE .. LIKE + // - BASIC - Supported except for WHERE .. LIKE + // - FULL - Supported for all WHERE .. + static const sal_Int32 UNSIGNED_ATTRIBUTE = 9; // boolean ==> is it unsigned? + // FIXED_PREC_SCALE = 10; boolean ==> can it be a money value? + static const sal_Int32 AUTO_INCREMENT = 11; // boolean ==> can it be used for + // an auto-increment value? + static const sal_Int32 MINIMUM_SCALE = 13; // short ==> minimum scale supported + static const sal_Int32 MAXIMUM_SCALE = 14; // short ==> maximum scale supported + static const sal_Int32 NUM_PREC_RADIX = 17; // long ==> usually 2 or 10 + + /* not filled so far + 3. LITERAL_PREFIX string ==> prefix used to quote a literal + (may be <NULL/>) + 4. LITERAL_SUFFIX string ==> suffix used to quote a literal + (may be <NULL/>) + 5. CREATE_PARAMS string ==> parameters used in creating the type (may be <NULL/>) + 12. LOCAL_TYPE_NAME string ==> localized version of type name (may be <NULL/>) + 15, SQL_DATA_TYPE long ==> unused + 16. SQL_DATETIME_SUB long ==> unused + */ + Reference< XRow > xRow( rs, UNO_QUERY_THROW ); + while( rs->next() ) + { + std::vector< Any > row(18); + + sal_Int32 dataType =typeNameToDataType(xRow->getString(5),xRow->getString(2)); + sal_Int32 precision = xRow->getString(3).toInt32(); + + if( dataType == css::sdbc::DataType::CHAR || + ( dataType == css::sdbc::DataType::VARCHAR && + xRow->getString(TYPE_NAME+1).equalsIgnoreAsciiCase("varchar") ) ) + { + // reflect varchar as varchar with upper limit ! + //NOTE: the sql spec requires varchar to have an upper limit, however + // in postgresql the upper limit is optional, no limit means unlimited + // length (=1GB). + precision = 0x40000000; // about 1 GB, see character type docs in postgresql + row[CREATE_PARAMS] <<= OUString("length"); + } + else if( dataType == css::sdbc::DataType::NUMERIC ) + { + precision = 1000; + row[CREATE_PARAMS] <<= OUString("length, scale"); + } + + row[TYPE_NAME] <<= construct_full_typename(xRow->getString(6), xRow->getString(1)); + row[DATA_TYPE] <<= OUString::number(dataType); + row[PRECISION] <<= OUString::number( precision ); + sal_Int32 nullable = xRow->getBoolean(4) ? + css::sdbc::ColumnValue::NO_NULLS : + css::sdbc::ColumnValue::NULLABLE; + row[NULLABLE] <<= OUString::number(nullable); + row[CASE_SENSITIVE] <<= OUString::number(1); + row[SEARCHABLE] <<= OUString::number( calcSearchable( dataType ) ); + row[UNSIGNED_ATTRIBUTE] <<= OUString("0"); + if( css::sdbc::DataType::INTEGER == dataType || + css::sdbc::DataType::BIGINT == dataType ) + row[AUTO_INCREMENT] <<= OUString("1"); // TODO + else + row[AUTO_INCREMENT] <<= OUString("0"); // TODO + row[MINIMUM_SCALE] <<= OUString("0"); // TODO: what is this ? + row[MAXIMUM_SCALE] <<= OUString::number( getMaxScale( dataType ) ); + row[NUM_PREC_RADIX] <<= OUString("10"); // TODO: what is this ? + vec.push_back( row ); + } + } +} + + +css::uno::Reference< XResultSet > DatabaseMetaData::getTypeInfo( ) +{ + // Note: Indexes start at 0 (in the API doc, they start at 1) + MutexGuard guard( m_xMutex->GetMutex() ); + + SAL_INFO("connectivity.postgresql", "DatabaseMetaData::getTypeInfo() got called"); + + Reference< XStatement > statement = m_origin->createStatement(); + Reference< XResultSet > rs = statement->executeQuery( + "SELECT pg_type.typname AS typname," //1 + "pg_type.typtype AS typtype," //2 + "pg_type.typlen AS typlen," //3 + "pg_type.typnotnull AS typnotnull," //4 + "pg_type.typname AS typname, " //5 + "pg_namespace.nspname as typns " //6 + "FROM pg_type LEFT JOIN pg_namespace ON pg_type.typnamespace=pg_namespace.oid " + "WHERE pg_type.typtype = 'b' " + "OR pg_type.typtype = 'p'" + ); + + std::vector< std::vector<Any> > vec; + pgTypeInfo2ResultSet( vec, rs ); + + // check for domain types + rs = statement->executeQuery( + "SELECT t1.typname as typname," + "t2.typtype AS typtype," + "t2.typlen AS typlen," + "t2.typnotnull AS typnotnull," + "t2.typname as realtypname, " + "pg_namespace.nspname as typns " + "FROM pg_type as t1 LEFT JOIN pg_type AS t2 ON t1.typbasetype=t2.oid LEFT JOIN pg_namespace ON t1.typnamespace=pg_namespace.oid " + "WHERE t1.typtype = 'd'" ); + pgTypeInfo2ResultSet( vec, rs ); + + std::sort( vec.begin(), vec.end(), TypeInfoByDataTypeSorter() ); + + return new SequenceResultSet( + m_xMutex, + *this, + std::vector(getStatics().typeinfoColumnNames), + std::move(vec), + m_pSettings->tc, + &( getStatics().typeInfoMetaData )); +} + + +css::uno::Reference< XResultSet > DatabaseMetaData::getIndexInfo( + const css::uno::Any& , + const OUString& schema, + const OUString& table, + sal_Bool unique, + sal_Bool ) +{ + //LEM TODO: review + MutexGuard guard( m_xMutex->GetMutex() ); + + /* + 1. TABLE_CAT string -> table catalog (may be NULL ) + 2. TABLE_SCHEM string -> table schema (may be NULL ) + 3. TABLE_NAME string -> table name + 4. NON_UNIQUE boolean -> Can index values be non-unique? + false when TYPE is tableIndexStatistic + 5. INDEX_QUALIFIER string -> index catalog (may be NULL ); + NULL when TYPE is tableIndexStatistic + 6. INDEX_NAME string -> index name; NULL when TYPE is tableIndexStatistic + 7. TYPE short -> index type: + * 0 - this identifies table statistics that are returned + in conjunction with a table's index descriptions + * CLUSTERED - this is a clustered index + * HASHED - this is a hashed index + * OTHER - this is some other style of index + 8. ORDINAL_POSITION short -> column sequence number within index; + zero when TYPE is tableIndexStatistic + 9. COLUMN_NAME string -> column name; NULL when TYPE is tableIndexStatistic + 10. ASC_OR_DESC string -> column sort sequence, "A"= ascending, + "D" = descending, may be NULL if sort sequence + is not supported; NULL when TYPE is tableIndexStatistic + 11. CARDINALITY long -> When TYPE is tableIndexStatistic, then this is + the number of rows in the table; otherwise, it + is the number of unique values in the index. + 12. PAGES long -> When TYPE is tableIndexStatistic then this is + the number of pages used for the table, otherwise + it is the number of pages used for the current index. + 13. FILTER_CONDITION string -> Filter condition, if any. (may be NULL ) + + */ + static const sal_Int32 C_SCHEMA = 1; + static const sal_Int32 C_TABLENAME = 2; + static const sal_Int32 C_INDEXNAME = 3; + static const sal_Int32 C_IS_CLUSTERED = 4; + static const sal_Int32 C_IS_UNIQUE = 5; + // C_IS_PRIMARY = 6 + static const sal_Int32 C_COLUMNS = 7; + + static const sal_Int32 R_TABLE_SCHEM = 1; + static const sal_Int32 R_TABLE_NAME = 2; + static const sal_Int32 R_NON_UNIQUE = 3; + static const sal_Int32 R_INDEX_NAME = 5; + static const sal_Int32 R_TYPE = 6; + static const sal_Int32 R_ORDINAL_POSITION = 7; + static const sal_Int32 R_COLUMN_NAME = 8; + + Reference< XPreparedStatement > stmt = m_origin->prepareStatement( + "SELECT nspname, " // 1 + "pg_class.relname, " // 2 + "class2.relname, " // 3 + "indisclustered, " // 4 + "indisunique, " // 5 + "indisprimary, " // 6 + "indkey " // 7 + "FROM pg_index INNER JOIN pg_class ON indrelid = pg_class.oid " + "INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid " + "INNER JOIN pg_class as class2 ON pg_index.indexrelid = class2.oid " + "WHERE nspname = ? AND pg_class.relname = ?" ); + + Reference< XParameters > param ( stmt, UNO_QUERY_THROW ); + param->setString( 1, schema ); + param->setString( 2, table ); + Reference< XResultSet > rs = stmt->executeQuery(); + Reference< XRow > xRow ( rs, UNO_QUERY_THROW ); + + std::vector< std::vector<Any> > vec; + while( rs->next() ) + { + std::vector< sal_Int32 > columns = parseIntArray( xRow->getString(C_COLUMNS) ); + Reference< XPreparedStatement > columnsStmt = m_origin->prepareStatement( + "SELECT attnum, attname " + "FROM pg_attribute " + " INNER JOIN pg_class ON attrelid = pg_class.oid " + " INNER JOIN pg_namespace ON pg_class.relnamespace=pg_namespace.oid " + " WHERE pg_namespace.nspname=? AND pg_class.relname=?" ); + Reference< XParameters > paramColumn ( columnsStmt, UNO_QUERY_THROW ); + OUString currentSchema = xRow->getString( C_SCHEMA ); + OUString currentTable = xRow->getString( C_TABLENAME ); + OUString currentIndexName = xRow->getString( C_INDEXNAME ); + bool isNonUnique = ! xRow->getBoolean( C_IS_UNIQUE ); + sal_Int32 indexType = xRow->getBoolean( C_IS_CLUSTERED ) ? + css::sdbc::IndexType::CLUSTERED : + css::sdbc::IndexType::HASHED; + + paramColumn->setString( C_SCHEMA, currentSchema ); + paramColumn->setString( C_TABLENAME, currentTable ); + + Reference< XResultSet > rsColumn = columnsStmt->executeQuery(); + Reference< XRow > rowColumn( rsColumn, UNO_QUERY_THROW ); + while( rsColumn->next() ) + { + auto findIt = std::find( columns.begin(), columns.end(), rowColumn->getInt( 1 ) ); + if( findIt != columns.end() && ( ! isNonUnique || ! unique ) ) + { + std::vector< Any > result( 13 ); + result[R_TABLE_SCHEM] <<= currentSchema; + result[R_TABLE_NAME] <<= currentTable; + result[R_INDEX_NAME] <<= currentIndexName; + result[R_NON_UNIQUE] <<= isNonUnique; + result[R_TYPE] <<= indexType; + result[R_COLUMN_NAME] <<= rowColumn->getString(2); + sal_Int32 nPos = static_cast<sal_Int32>(findIt - columns.begin() +1); // MSVC++ nonsense + result[R_ORDINAL_POSITION] <<= nPos; + vec.push_back( result ); + } + } + } + return new SequenceResultSet( + m_xMutex, *this, std::vector(getStatics().indexinfoColumnNames), + std::move(vec), + m_pSettings->tc ); +} + +sal_Bool DatabaseMetaData::supportsResultSetType( sal_Int32 setType ) +{ + if ( setType == css::sdbc::ResultSetType::SCROLL_SENSITIVE ) + return false; + else + return true; +} + +sal_Bool DatabaseMetaData::supportsResultSetConcurrency( + sal_Int32 setType, sal_Int32 ) +{ + if ( ! supportsResultSetType( setType ) ) + return false; + else + return true; +} + +sal_Bool DatabaseMetaData::ownUpdatesAreVisible( sal_Int32 /* setType */ ) +{ + return true; +} + +sal_Bool DatabaseMetaData::ownDeletesAreVisible( sal_Int32 /* setType */ ) +{ + return true; +} + +sal_Bool DatabaseMetaData::ownInsertsAreVisible( sal_Int32 /* setType */ ) +{ + return true; +} + +sal_Bool DatabaseMetaData::othersUpdatesAreVisible( sal_Int32 /* setType */ ) +{ + return false; +} + +sal_Bool DatabaseMetaData::othersDeletesAreVisible( sal_Int32 /* setType */ ) +{ + return false; +} + +sal_Bool DatabaseMetaData::othersInsertsAreVisible( sal_Int32 /* setType */ ) +{ + return false; +} + +sal_Bool DatabaseMetaData::updatesAreDetected( sal_Int32 /* setType */ ) +{ + return false; +} + +sal_Bool DatabaseMetaData::deletesAreDetected( sal_Int32 /* setType */ ) +{ + return false; +} +sal_Bool DatabaseMetaData::insertsAreDetected( sal_Int32 /* setType */ ) +{ + return false; +} + +sal_Bool DatabaseMetaData::supportsBatchUpdates( ) +{ + return true; +} + +css::uno::Reference< XResultSet > DatabaseMetaData::getUDTs( const css::uno::Any&, const OUString&, const OUString&, const css::uno::Sequence< sal_Int32 >& ) +{ + //LEM TODO: implement! See JDBC driver + MutexGuard guard( m_xMutex->GetMutex() ); + return new SequenceResultSet( + m_xMutex, *this, std::vector< OUString >(), std::vector< std::vector< Any > >(), m_pSettings->tc ); +} + +css::uno::Reference< css::sdbc::XConnection > DatabaseMetaData::getConnection() +{ + return m_origin; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |