<commit_before>require_relative '../fdw'

module Carto
  class Connector

    # Base class for ODBC-based providers using odbc_fdw
    #
    # Requirements:
    #   * odbc_fdw extension must be installed in the user database
    #
    # Parameters: except for connection, these parameters correspond to options of odbc_fdw which are not connection
    # attributes (odbc_ prefixed options).
    #   * connection: connection attributes; the content is defined by derived classes and will be used
    #     to generate the odbc_ options of odbc_fdw.
    #   * schema: schema name of the remote schema
    #   * table: name of the remote table to import (if no sql_query is given)
    #     and/or local name of the imported result table
    #   * sql_query (optional): SQL code to be executed remotely to produce the dataset to be imported.
    #     If missing, schema.table will be imported.
    #   * sql_count (optional): SQL code to be executed remotely to compute the number of rows of the dataset
    #     to be imported. This shouldn't be needed in general, but could be needed depending on the SQL dialect
    #     that the driver supports.
    #   * encoding (optional): character encoding used by the external database; default is UTF-8.
    #     The encoding names accepted are those accepted by PostgreSQL.
    #
    # Derived classes for specific ODBC drivers will typically redine at least these methods that map
    # parameters to ODBC connection attributes:
    #
    # * `fixed_odbc_attributes` defines fixed values for odbc attributes
    # * `connection_odbc_attributes` maps connection parameters to odbc attributes
    # * `odbc_attributes` maps (non-connection) parameters to odbc attributes
    #
    class OdbcProvider < FdwProvider

      def initialize(context, params)
        super
        @columns = @params[:columns]
        @columns = @columns.split(',').map(&:strip) if @columns
        @connection = Parameters.new(
          @params[:connection],
          required: odbc_attributes_for_required_connection_parameters.keys,
          optional: odbc_attributes_for_optional_connection_parameters.keys
        )
      end

      def errors(only_for: nil)
        super + @connection.errors(parameters_term: 'connection parameters')
      end

      required_parameters %I(table connection)
      optional_parameters %I(import_as schema sql_query sql_count encoding columns)

      # ODBC attributes for (non-connection) parameters: { name: :internal_name }#
      # The :internal_name is what is passed to the driver (through odbc_fdw 'odbc_' options)
      # The :name is the case-insensitive parameter received here trhough the API
      # Optional parameters are defined as { name: { internal_name: default_value } }
      # This can be redefined as needed in derived classes.
      def odbc_attributes
        {}
      end

      # ODBC attributes for connector parameters: { name: :internal_name }#
      # The :internal_name is what is passed to the driver (through odbc_fdw 'odbc_' options)
      # The :name is the case-insensitive parameter received here trhough the API inside `connector`.
      # Optional parameters are defined as { name: { internal_name: default_value } }
      # This can be redefined as needed in derived classes.
      def connection_odbc_attributes
        {}
      end

      # ODBC attributes with fixed values: { internal_name: value }
      # which are always passed to the driver
      # This can be redefined as needed in derived classes.
      def fixed_odbc_attributes
        {}
      end

      # Return table options to connect to a query (used for checking the connection)
      def options_for_query(_query)
        must_be_defined_in_derived_class
      end

      def table_name
        @params[:import_as] || @params[:table]
      end

      def remote_table_name
        @params[:table] || @params[:import_as]
      end

      def foreign_table_name_for(server_name, name = nil)
        fdw_adjusted_table_name("#{unique_prefix_for(server_name)}#{name || remote_table_name}")
      end

      def unique_prefix_for(server_name)
        # server_name should already be unique
        "#{server_name}_"
      end

      def remote_schema_name
        schema = table_options[:schema]
        schema = 'public' if schema.blank?
        schema
      end

      def fdw_create_server(server_name)
        sql = fdw_create_server_sql 'odbc_fdw', server_name, server_options
        execute_as_superuser sql
      end

      def fdw_create_usermaps(server_name)
        execute_as_superuser fdw_create_usermap_sql(server_name, @connector_context.database_username, user_options)
        execute_as_superuser fdw_create_usermap_sql(server_name, 'postgres', user_options)
      end

      def fdw_create_foreign_table(server_name)
        cmds = []
        foreign_table_name = foreign_table_name_for(server_name)
        if @columns.present?
          cmds << fdw_create_foreign_table_sql(
            server_name, foreign_table_schema, foreign_table_name, @columns, table_options<commit_msg>Use local table name for foreign table<commit_after>require_relative '../fdw'

module Carto
  class Connector

    # Base class for ODBC-based providers using odbc_fdw
    #
    # Requirements:
    #   * odbc_fdw extension must be installed in the user database
    #
    # Parameters: except for connection, these parameters correspond to options of odbc_fdw which are not connection
    # attributes (odbc_ prefixed options).
    #   * connection: connection attributes; the content is defined by derived classes and will be used
    #     to generate the odbc_ options of odbc_fdw.
    #   * schema: schema name of the remote schema
    #   * table: name of the remote table to import (if no sql_query is given)
    #     and/or local name of the imported result table
    #   * sql_query (optional): SQL code to be executed remotely to produce the dataset to be imported.
    #     If missing, schema.table will be imported.
    #   * sql_count (optional): SQL code to be executed remotely to compute the number of rows of the dataset
    #     to be imported. This shouldn't be needed in general, but could be needed depending on the SQL dialect
    #     that the driver supports.
    #   * encoding (optional): character encoding used by the external database; default is UTF-8.
    #     The encoding names accepted are those accepted by PostgreSQL.
    #
    # Derived classes for specific ODBC drivers will typically redine at least these methods that map
    # parameters to ODBC connection attributes:
    #
    # * `fixed_odbc_attributes` defines fixed values for odbc attributes
    # * `connection_odbc_attributes` maps connection parameters to odbc attributes
    # * `odbc_attributes` maps (non-connection) parameters to odbc attributes
    #
    class OdbcProvider < FdwProvider

      def initialize(context, params)
        super
        @columns = @params[:columns]
        @columns = @columns.split(',').map(&:strip) if @columns
        @connection = Parameters.new(
          @params[:connection],
          required: odbc_attributes_for_required_connection_parameters.keys,
          optional: odbc_attributes_for_optional_connection_parameters.keys
        )
      end

      def errors(only_for: nil)
        super + @connection.errors(parameters_term: 'connection parameters')
      end

      required_parameters %I(table connection)
      optional_parameters %I(import_as schema sql_query sql_count encoding columns)

      # ODBC attributes for (non-connection) parameters: { name: :internal_name }#
      # The :internal_name is what is passed to the driver (through odbc_fdw 'odbc_' options)
      # The :name is the case-insensitive parameter received here trhough the API
      # Optional parameters are defined as { name: { internal_name: default_value } }
      # This can be redefined as needed in derived classes.
      def odbc_attributes
        {}
      end

      # ODBC attributes for connector parameters: { name: :internal_name }#
      # The :internal_name is what is passed to the driver (through odbc_fdw 'odbc_' options)
      # The :name is the case-insensitive parameter received here trhough the API inside `connector`.
      # Optional parameters are defined as { name: { internal_name: default_value } }
      # This can be redefined as needed in derived classes.
      def connection_odbc_attributes
        {}
      end

      # ODBC attributes with fixed values: { internal_name: value }
      # which are always passed to the driver
      # This can be redefined as needed in derived classes.
      def fixed_odbc_attributes
        {}
      end

      # Return table options to connect to a query (used for checking the connection)
      def options_for_query(_query)
        must_be_defined_in_derived_class
      end

      def table_name
        @params[:import_as] || @params[:table]
      end

      def foreign_table_name_for(server_name, name = nil)
        fdw_adjusted_table_name("#{unique_prefix_for(server_name)}#{name || table_name}")
      end

      def unique_prefix_for(server_name)
        # server_name should already be unique
        "#{server_name}_"
      end

      def remote_schema_name
        schema = table_options[:schema]
        schema = 'public' if schema.blank?
        schema
      end

      def fdw_create_server(server_name)
        sql = fdw_create_server_sql 'odbc_fdw', server_name, server_options
        execute_as_superuser sql
      end

      def fdw_create_usermaps(server_name)
        execute_as_superuser fdw_create_usermap_sql(server_name, @connector_context.database_username, user_options)
        execute_as_superuser fdw_create_usermap_sql(server_name, 'postgres', user_options)
      end

      def fdw_create_foreign_table(server_name)
        cmds = []
        foreign_table_name = foreign_table_name_for(server_name)
        if @columns.present?
          cmds << fdw_create_foreign_table_sql(
            server_name, foreign_table_schema, foreign_table_name, @columns, table_options
{'fraction_non_alphanumeric': 0.06216484607745779, 'fraction_numerical': 0.00019860973187686197, 'mean_word_length': 3.009156050955414, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3425673', 'n_tokens_mistral': 2748, 'n_tokens_neox': 2690, 'n_words': 940}