PHOENIX-5880 Add SQLAchemy support to python driver
also:
* PHOENIX-5846 Let the python client parse options from the JDBC URL
* compatibility fixes for python < 3.6
* limited support for old setuptools
* use default SPNEGO config from requests_gssapi
Closes #32
diff --git a/python/phoenixdb/Dockerfile b/python/phoenixdb/Dockerfile
index 7edaced..e0beaee 100644
--- a/python/phoenixdb/Dockerfile
+++ b/python/phoenixdb/Dockerfile
@@ -3,7 +3,7 @@
RUN apt-get update && apt-get install -y krb5-user libkrb5-dev
ENV PHOENIXDB_TEST_DB_URL=http://host.docker.internal:8765
-ENV PHOENIXDB_TEST_DB_TRUSTSTORE $PHOENIXDB_TEST_DB_TRUSTSTORE
-ENV PHOENIXDB_TEST_DB_AUTHENTICATION $PHOENIXDB_TEST_DB_AUTHENTICATION
-ENV PHOENIXDB_TEST_DB_AVATICA_USER $PHOENIXDB_TEST_DB_AVATICA_USER
-ENV PHOENIXDB_TEST_DB_AVATICA_PASSWORD $PHOENIXDB_TEST_DB_AVATICA_PASSWORD
\ No newline at end of file
+ENV PHOENIXDB_TEST_DB_TRUSTSTORE=
+ENV PHOENIXDB_TEST_DB_AUTHENTICATION=
+ENV PHOENIXDB_TEST_DB_AVATICA_USER=
+ENV PHOENIXDB_TEST_DB_AVATICA_PASSWORD=
\ No newline at end of file
diff --git a/python/phoenixdb/NEWS.rst b/python/phoenixdb/NEWS.rst
index 089e7ed..db0ab0f 100644
--- a/python/phoenixdb/NEWS.rst
+++ b/python/phoenixdb/NEWS.rst
@@ -4,6 +4,7 @@
Unreleased
----------
- Replaced bundled requests_kerberos with request_gssapi library
+- Use default SPNEGO Auth settings from request_gssapi
- Refactored authentication code
- Added support for specifying server certificate
- Added support for BASIC and DIGEST authentication
@@ -14,6 +15,7 @@
- Refactored test suite
- Removed shell example, as it was python2 only
- Updated documentation
+- Added SQLAlchemy dialect
Version 0.7
-----------
diff --git a/python/phoenixdb/README.rst b/python/phoenixdb/README.rst
index 8f321af..793f8ee 100644
--- a/python/phoenixdb/README.rst
+++ b/python/phoenixdb/README.rst
@@ -57,12 +57,22 @@
python setup.py develop
You can start a Phoenix QueryServer instance on http://localhost:8765 for testing by running
-the following command in the phoenix-queryserver directory:
+the following command in the phoenix-queryserver directory::
mvn clean verify -am -pl queryserver-it -Dtest=foo \
-Dit.test=QueryServerBasicsIT\#startLocalPQS \
-Ddo.not.randomize.pqs.port=true -Dstart.unsecure.pqs=true
+You can start a secure (https+kerberos) Phoenix QueryServer instance on https://localhost:8765
+for testing by running the following command in the phoenix-queryserver directory::
+
+ mvn clean verify -am -pl queryserver-it -Dtest=foo \
+ -Dit.test=SecureQueryServerPhoenixDBIT\#startLocalPQS \
+ -Ddo.not.randomize.pqs.port=true -Dstart.secure.pqs=true
+
+this will also create a shell script in queryserver-it/target/krb_setup.sh, that you can use to set
+up the environment for the tests.
+
If you want to use the library without installing the phoenixdb library, you can use
the `PYTHONPATH` environment variable to point to the library directly::
diff --git a/python/phoenixdb/phoenixdb/__init__.py b/python/phoenixdb/phoenixdb/__init__.py
index a90be99..4f5378e 100644
--- a/python/phoenixdb/phoenixdb/__init__.py
+++ b/python/phoenixdb/phoenixdb/__init__.py
@@ -12,7 +12,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-from gssapi import mechs
+import sys
from phoenixdb import errors, types
from phoenixdb.avatica import AvaticaClient
@@ -22,8 +22,13 @@
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
-from requests_gssapi import HTTPSPNEGOAuth, OPTIONAL
+from requests_gssapi import HTTPSPNEGOAuth
+if sys.version_info.major == 3:
+ from urllib.parse import urlencode, urlparse, urlunparse, parse_qs
+else:
+ from urllib import urlencode
+ from urlparse import urlparse, urlunparse, parse_qs
__all__ = ['connect', 'apilevel', 'threadsafety', 'paramstyle'] + types.__all__ + errors.__all__
@@ -96,14 +101,41 @@
:class:`~phoenixdb.connection.Connection` object.
"""
- spnego = mechs.Mechanism.from_sasl_name("SPNEGO")
+ url_parsed = urlparse(url)
+ url_params = parse_qs(url_parsed.query, keep_blank_values=True)
+
+ # Parse supported JDBC compatible options from URL. args have precendece
+ rebuild = False
+ if auth is None and authentication is None and 'authentication' in url_params:
+ authentication = url_params['authentication'][0]
+ del url_params['authentication']
+ rebuild = True
+
+ if avatica_user is None and 'avatica_user' in url_params:
+ avatica_user = url_params['avatica_user'][0]
+ del url_params['avatica_user']
+ rebuild = True
+
+ if avatica_password is None and 'avatica_password' in url_params:
+ avatica_password = url_params['avatica_password'][0]
+ del url_params['avatica_password']
+ rebuild = True
+
+ if verify is None and truststore is None and 'truststore' in url_params:
+ truststore = url_params['truststore'][0]
+ del url_params['truststore']
+ rebuild = True
+
+ if rebuild:
+ url_parsed._replace(query=urlencode(url_params, True))
+ url = urlunparse(url_parsed)
if auth == "SPNEGO":
# Special case for backwards compatibility
- auth = HTTPSPNEGOAuth(mutual_authentication=OPTIONAL, mech=spnego)
+ auth = HTTPSPNEGOAuth(opportunistic_auth=True)
elif auth is None and authentication is not None:
if authentication == "SPNEGO":
- auth = HTTPSPNEGOAuth(mutual_authentication=OPTIONAL, mech=spnego, opportunistic_auth=True)
+ auth = HTTPSPNEGOAuth(opportunistic_auth=True)
elif authentication == "BASIC" and avatica_user is not None and avatica_password is not None:
auth = HTTPBasicAuth(avatica_user, avatica_password)
elif authentication == "DIGEST" and avatica_user is not None and avatica_password is not None:
diff --git a/python/phoenixdb/phoenixdb/connection.py b/python/phoenixdb/phoenixdb/connection.py
index 1cd562b..0930115 100644
--- a/python/phoenixdb/phoenixdb/connection.py
+++ b/python/phoenixdb/phoenixdb/connection.py
@@ -130,9 +130,7 @@
"""Sets one or more parameters in the current connection.
:param autocommit:
- Switch the connection to autocommit mode. With the current
- version, you need to always enable this, because
- :meth:`commit` is not implemented.
+ Switch the connection to autocommit mode.
:param readonly:
Switch the connection to read-only mode.
diff --git a/python/phoenixdb/phoenixdb/sqlalchemy_phoenix.py b/python/phoenixdb/phoenixdb/sqlalchemy_phoenix.py
new file mode 100644
index 0000000..1d5da0a
--- /dev/null
+++ b/python/phoenixdb/phoenixdb/sqlalchemy_phoenix.py
@@ -0,0 +1,257 @@
+# Copyright 2017 Dimitri Capitaine
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from sqlalchemy import types
+from sqlalchemy.engine.default import DefaultDialect, DefaultExecutionContext
+from sqlalchemy.exc import CompileError
+from sqlalchemy.sql.compiler import DDLCompiler
+from sqlalchemy.types import BIGINT, BOOLEAN, CHAR, DATE, DECIMAL, FLOAT, INTEGER, NUMERIC,\
+ SMALLINT, TIME, TIMESTAMP, VARBINARY, VARCHAR
+
+import phoenixdb
+import re
+import sys
+
+if sys.version_info.major == 3:
+ from urllib.parse import urlunsplit, SplitResult, urlencode
+else:
+ from urllib import urlencode
+ from urlparse import urlunsplit, SplitResult
+
+
+class PhoenixDDLCompiler(DDLCompiler):
+
+ def visit_primary_key_constraint(self, constraint):
+ if constraint.name is None:
+ raise CompileError("can't create primary key without a name")
+ return DDLCompiler.visit_primary_key_constraint(self, constraint)
+
+
+AUTOCOMMIT_REGEXP = re.compile(
+ r"\s*(?:UPDATE|UPSERT|CREATE|DELETE|DROP|ALTER)", re.I | re.UNICODE
+)
+
+
+class PhoenixExecutionContext(DefaultExecutionContext):
+
+ def should_autocommit_text(self, statement):
+ return AUTOCOMMIT_REGEXP.match(statement)
+
+
+class PhoenixDialect(DefaultDialect):
+ '''Phoenix dialect
+
+ dialect:: phoenix
+ :name: Phoenix
+
+ note::
+
+ The Phoenix dialect for SQLAlchemy is incomplete. It implements the functions required by Hue
+ for basic operation, but little else.
+
+ Connecting
+ ----------
+
+ The connection URL has the format of phoenix://host:port
+
+ This format does not allow for specifying the http scheme, or the URL path the the server uses.
+ Setting tls=True sets the server URL scheme to https.
+ If the path arg is set , it used as the path of the server URL.
+
+ The phoenix-specific authentication options can be set via the standard connect_args argument.
+
+ Connecting to an unsecure server::
+
+ create_engine('phoenix://localhost:8765')
+
+ Connecting to a secure server via SPNEGO (after kinit)::
+
+ create_engine('phoenix://localhost:8765', tls=True, connect_args={'authentication': 'SPNEGO'})
+
+ Connecting to a secure server via Knox::
+
+ create_engine('phoenix://localhost:8765', tls=True, path='/gateway/avatica/'\
+ connect_args={'authentication':'BASIC', 'avatica_user':'user', 'avatica_password':'password'})
+ '''
+
+ name = "phoenix"
+
+ driver = "phoenixdb"
+
+ ddl_compiler = PhoenixDDLCompiler
+
+ execution_ctx_cls = PhoenixExecutionContext
+
+ def __init__(self, tls=False, path='/', **opts):
+ # There is no way to pass these via the SqlAlchemy url object
+ self.tls = tls
+ self.path = path
+ super(PhoenixDialect, self).__init__(self, **opts)
+
+ @classmethod
+ def dbapi(cls):
+ return phoenixdb
+
+ def create_connect_args(self, url):
+ phoenix_url = urlunsplit(SplitResult(
+ scheme='https' if self.tls else 'http',
+ netloc='{}:{}'.format(url.host, 8765 if url.port is None else url.port),
+ path=self.path,
+ query=urlencode(url.query),
+ fragment='',
+ ))
+ return [phoenix_url], {}
+
+ def has_table(self, connection, table_name, schema=None):
+ if schema is None:
+ query = "SELECT 1 FROM system.catalog WHERE table_name = ? LIMIT 1"
+ params = [table_name.upper()]
+ else:
+ query = "SELECT 1 FROM system.catalog WHERE table_name = ? AND TABLE_SCHEM = ? LIMIT 1"
+ params = [table_name.upper(), schema.upper()]
+ return connection.execute(query, params).first() is not None
+
+ def get_schema_names(self, connection, **kw):
+ query = "SELECT DISTINCT TABLE_SCHEM FROM SYSTEM.CATALOG"
+ return [row[0] for row in connection.execute(query)]
+
+ def get_table_names(self, connection, schema=None, **kw):
+ if schema is None:
+ query = "SELECT DISTINCT table_name FROM SYSTEM.CATALOG"
+ params = []
+ else:
+ query = "SELECT DISTINCT table_name FROM SYSTEM.CATALOG WHERE TABLE_SCHEM = ? "
+ params = [schema.upper()]
+ return [row[0] for row in connection.execute(query, params)]
+
+ def get_columns(self, connection, table_name, schema=None, **kw):
+ if schema is None:
+ query = "SELECT COLUMN_NAME, DATA_TYPE, NULLABLE " \
+ "FROM system.catalog " \
+ "WHERE table_name = ? " \
+ "ORDER BY ORDINAL_POSITION"
+ params = [table_name.upper()]
+ else:
+ query = "SELECT COLUMN_NAME, DATA_TYPE, NULLABLE " \
+ "FROM system.catalog " \
+ "WHERE TABLE_SCHEM = ? " \
+ "AND table_name = ? " \
+ "ORDER BY ORDINAL_POSITION"
+ params = [schema.upper(), table_name.upper()]
+
+ # get all of the fields for this table
+ c = connection.execute(query, params)
+ cols = []
+ # first always none
+ c.fetchone()
+ while True:
+ row = c.fetchone()
+ if row is None:
+ break
+ name = row[0]
+ col_type = COLUMN_DATA_TYPE[row[1]]
+ nullable = row[2] == 1 if True else False
+
+ col_d = {
+ 'name': name,
+ 'type': col_type,
+ 'nullable': nullable,
+ 'default': None
+ }
+
+ cols.append(col_d)
+ return cols
+
+ # TODO This should be possible to implement
+ def get_pk_constraint(self, conn, table_name, schema=None, **kw):
+ return []
+
+ def get_foreign_keys(self, conn, table_name, schema=None, **kw):
+ return []
+
+ # TODO This should be possible to implement
+ def get_indexes(self, conn, table_name, schema=None, **kw):
+ return []
+
+
+class TINYINT(types.Integer):
+ __visit_name__ = "SMALLINT"
+
+
+class UNSIGNED_TINYINT(types.Integer):
+ __visit_name__ = "SMALLINT"
+
+
+class UNSIGNED_INTEGER(types.Integer):
+ __visit_name__ = "INTEGER"
+
+
+class DOUBLE(types.FLOAT):
+ __visit_name__ = "FLOAT"
+
+
+class UNSIGNED_DOUBLE(types.FLOAT):
+ __visit_name__ = "FLOAT"
+
+
+class UNSIGNED_FLOAT(types.FLOAT):
+ __visit_name__ = "FLOAT"
+
+
+class UNSIGNED_LONG(types.BIGINT):
+ __visit_name__ = "BIGINT"
+
+
+class UNSIGNED_TIME(types.TIME):
+ __visit_name__ = "TIME"
+
+
+class UNSIGNED_DATE(types.DATE):
+ __visit_name__ = "DATE"
+
+
+class UNSIGNED_TIMESTAMP(types.TIMESTAMP):
+ __visit_name__ = "TIMESTAMP"
+
+
+class ROWID (types.String):
+ __visit_name__ = "VARCHAR"
+
+
+COLUMN_DATA_TYPE = {
+ -6: TINYINT,
+ -5: BIGINT,
+ -3: VARBINARY,
+ 1: CHAR,
+ 2: NUMERIC,
+ 3: DECIMAL,
+ 4: INTEGER,
+ 5: SMALLINT,
+ 6: FLOAT,
+ 8: DOUBLE,
+ 9: UNSIGNED_INTEGER,
+ 10: UNSIGNED_LONG,
+ 11: UNSIGNED_TINYINT,
+ 12: VARCHAR,
+ 13: ROWID,
+ 14: UNSIGNED_FLOAT,
+ 15: UNSIGNED_DOUBLE,
+ 16: BOOLEAN,
+ 18: UNSIGNED_TIME,
+ 19: UNSIGNED_DATE,
+ 20: UNSIGNED_TIMESTAMP,
+ 91: DATE,
+ 92: TIME,
+ 93: TIMESTAMP
+}
diff --git a/python/phoenixdb/phoenixdb/tests/__init__.py b/python/phoenixdb/phoenixdb/tests/__init__.py
index b541929..bacc8da 100644
--- a/python/phoenixdb/phoenixdb/tests/__init__.py
+++ b/python/phoenixdb/phoenixdb/tests/__init__.py
@@ -18,8 +18,7 @@
import phoenixdb
-TEST_DB_URL = os.environ.get('PHOENIXDB_TEST_DB_URL')
-# TEST_DB_URL = "http://localhost:8765"
+TEST_DB_URL = os.environ.get('PHOENIXDB_TEST_DB_URL', "http://localhost:8765")
TEST_DB_TRUSTSTORE = os.environ.get('PHOENIXDB_TEST_DB_TRUSTSTORE')
TEST_DB_AUTHENTICATION = os.environ.get('PHOENIXDB_TEST_DB_AUTHENTICATION')
TEST_DB_AVATICA_USER = os.environ.get('PHOENIXDB_TEST_DB_AVATICA_USER')
diff --git a/python/phoenixdb/phoenixdb/tests/test_db.py b/python/phoenixdb/phoenixdb/tests/test_db.py
index ea81e4a..04f19d9 100644
--- a/python/phoenixdb/phoenixdb/tests/test_db.py
+++ b/python/phoenixdb/phoenixdb/tests/test_db.py
@@ -83,5 +83,27 @@
self.assertEqual(cursor.fetchall(), [[1, 'text 1']])
def test_transaction(self):
- # Todo write some transaction tests
- pass
+ self.reopen(autocommit=False)
+ with self.conn.cursor() as cursor:
+ self.createTable("test", "CREATE TABLE {table} (id INTEGER PRIMARY KEY, text VARCHAR)")
+
+ cursor.execute("UPSERT INTO test VALUES (?, ?)", [1, 'one'])
+ cursor.execute("SELECT * FROM test ORDER BY id")
+ self.assertEqual(cursor.fetchall(), [])
+
+ self.conn.commit()
+ cursor.execute("SELECT * FROM test ORDER BY id")
+ self.assertEqual(cursor.fetchall(), [[1, 'one']])
+ self.assertEqual(self.conn.autocommit, False)
+
+ cursor.execute("UPSERT INTO test VALUES (?, ?)", [2, 'two'])
+ self.conn.rollback()
+ cursor.execute("SELECT * FROM test ORDER BY id")
+ self.assertEqual(cursor.fetchall(), [[1, 'one']])
+ self.assertEqual(self.conn.autocommit, False)
+
+ cursor.execute("UPSERT INTO test VALUES (?, ?)", [2, 'two'])
+ # Since we expose the JDBC semantics, this is an implicit commit
+ self.conn.autocommit = True
+ cursor.execute("SELECT * FROM test ORDER BY id")
+ self.assertEqual(cursor.fetchall(), [[1, 'one'], [2, 'two']])
diff --git a/python/phoenixdb/phoenixdb/tests/test_sqlalchemy.py b/python/phoenixdb/phoenixdb/tests/test_sqlalchemy.py
new file mode 100644
index 0000000..2cf7a57
--- /dev/null
+++ b/python/phoenixdb/phoenixdb/tests/test_sqlalchemy.py
@@ -0,0 +1,79 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+import sys
+
+import sqlalchemy as db
+from sqlalchemy import text
+
+from . import TEST_DB_URL, TEST_DB_AUTHENTICATION, TEST_DB_AVATICA_USER, TEST_DB_AVATICA_PASSWORD,\
+ TEST_DB_TRUSTSTORE
+
+if sys.version_info.major == 3:
+ from urllib.parse import urlparse, urlunparse
+else:
+ from urlparse import urlparse, urlunparse
+
+
+@unittest.skipIf(TEST_DB_URL is None, "these tests require the PHOENIXDB_TEST_DB_URL environment variable set to a clean database")
+class SQLAlchemyTest(unittest.TestCase):
+
+ def test_connection(self):
+ engine = self._create_engine()
+ # connection = engine.connect()
+ metadata = db.MetaData()
+ catalog = db.Table('CATALOG', metadata, autoload=True, autoload_with=engine)
+ self.assertIn('TABLE_NAME', catalog.columns.keys())
+
+ def test_textual(self):
+ engine = self._create_engine()
+ with engine.connect() as connection:
+ try:
+ connection.execute('drop table if exists ALCHEMY_TEST')
+ connection.execute(text('create table ALCHEMY_TEST (id integer primary key)'))
+ connection.execute(text('upsert into ALCHEMY_TEST values (42)'))
+ # SQLAlchemy autocommit should kick in
+ result = connection.execute(text('select * from ALCHEMY_TEST'))
+ row = result.fetchone()
+ self.assertEqual(row[0], 42)
+ finally:
+ connection.execute('drop table if exists ALCHEMY_TEST')
+
+ @unittest.skip("ORM feature not implemented")
+ def test_orm(self):
+ pass
+
+ def _create_engine(self):
+ ''''Massage the properties that we use for the DBAPI tests so that they apply to
+ SQLAlchemy'''
+
+ url_parts = urlparse(TEST_DB_URL)
+
+ tls = url_parts.scheme.lower == 'https'
+
+ url_parts = url_parts._replace(scheme='phoenix')
+
+ connect_args = dict()
+ if TEST_DB_AUTHENTICATION:
+ connect_args.update(authentication=TEST_DB_AUTHENTICATION)
+ if TEST_DB_AVATICA_USER:
+ connect_args.update(avatica_user=TEST_DB_AVATICA_USER)
+ if TEST_DB_AVATICA_PASSWORD:
+ connect_args.update(avatica_password=TEST_DB_AVATICA_PASSWORD)
+ if TEST_DB_TRUSTSTORE:
+ connect_args.update(trustore=TEST_DB_TRUSTSTORE)
+
+ return db.create_engine(urlunparse(url_parts), tls=tls, connect_args=connect_args)
diff --git a/python/phoenixdb/setup.py b/python/phoenixdb/setup.py
index d1f0ce5..f7e3124 100644
--- a/python/phoenixdb/setup.py
+++ b/python/phoenixdb/setup.py
@@ -17,6 +17,8 @@
# under the License.
#
from setuptools import setup, find_packages
+import setuptools
+import sys
cmdclass = {}
@@ -32,6 +34,31 @@
return f.read()
+if setuptools.__version__ < '20.8.1':
+ # Workaround for source install on old setuptools
+ # This won't be able to create a proper multi-version pacakage
+ install_requires=[
+ 'protobuf>=3.0.0',
+ 'requests',
+ 'requests-gssapi',
+ 'SQLAlchemy'
+ ]
+ if sys.version_info < (3,6):
+ install_requires.append('gssapi<1.6.0')
+ #Don't build the docs on an old stack
+ setup_requires=[]
+else:
+ install_requires=[
+ 'protobuf>=3.0.0',
+ 'requests',
+ 'requests-gssapi',
+ 'gssapi<1.6.0;python_version<"3.6"',
+ 'SQLAlchemy'
+ ]
+ setup_requires=[
+ 'Sphinx;python_version>="3.6"',
+ ],
+
version = "1.0.0.dev"
setup(
@@ -63,21 +90,19 @@
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
],
- install_requires=[
- 'protobuf>=3.0.0',
- 'requests',
- 'requests-gssapi'
- ],
+ install_requires=install_requires,
+ extras_require={
+ 'SQLAlchemy': ['SQLAlchemy'],
+ },
tests_require=[
+ 'SQLAlchemy',
'nose',
'flake8'
],
- setup_requires=[
- # Later versions don't work with python2.7
- 'Sphinx<2.0.0',
- # These are Sphinx dependencies, included only to be version managed for python2
- 'MarkupSafe<2.0.0',
- 'Jinja2<3.0.0',
- 'pyparsing<3.0.0'
- ]
+ setup_requires=setup_requires,
+ entry_points={
+ "sqlalchemy.dialects": [
+ "phoenix = phoenixdb.sqlalchemy_phoenix:PhoenixDialect"
+ ]
+ },
)
diff --git a/queryserver-it/src/it/java/org/apache/phoenix/end2end/SecureQueryServerPhoenixDBIT.java b/queryserver-it/src/it/java/org/apache/phoenix/end2end/SecureQueryServerPhoenixDBIT.java
index 95c32f5..b054105 100644
--- a/queryserver-it/src/it/java/org/apache/phoenix/end2end/SecureQueryServerPhoenixDBIT.java
+++ b/queryserver-it/src/it/java/org/apache/phoenix/end2end/SecureQueryServerPhoenixDBIT.java
@@ -245,7 +245,9 @@
conf.set(QueryServerProperties.QUERY_SERVER_KEYTAB_FILENAME_ATTRIB,
KEYTAB.getAbsolutePath());
conf.setBoolean(QueryServerProperties.QUERY_SERVER_DISABLE_KERBEROS_LOGIN, true);
- conf.setInt(QueryServerProperties.QUERY_SERVER_HTTP_PORT_ATTRIB, 0);
+ if(System.getProperty("do.not.randomize.pqs.port") == null) {
+ conf.setInt(QueryServerProperties.QUERY_SERVER_HTTP_PORT_ATTRIB, 0);
+ }
// Required so that PQS can impersonate the end-users to HBase
conf.set("hadoop.proxyuser.phoenixqs.groups", "*");
conf.set("hadoop.proxyuser.phoenixqs.hosts", "*");