Skip to content

Commit

Permalink
Disabling SQL compilation cache for Solr releases earlier than 9.0
Browse files Browse the repository at this point in the history
  • Loading branch information
aadel committed Jul 25, 2024
1 parent 20fb56e commit 091dc7c
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 82 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ To connect to Solr with SQLAlchemy, the following URL pattern can be used:
solr://<username>:<password>@<host>:<port>/solr/<collection>[?parameter=value]
```

_Note_: port 8983 is used when `port` in the URL is omitted

### Authentication

#### Basic Authentication
Expand Down Expand Up @@ -162,6 +164,7 @@ translates to `[2024-01-01T00:00:00Z TO *]`
| Aliases |||||||
| Built-in date range compilation |||||||
| `SELECT` _expression_ statements |||||||
| SQL compilation caching |||||||

## Use Cases

Expand Down
40 changes: 38 additions & 2 deletions src/sqlalchemy_solr/admin/solr_spec.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,51 @@
from requests import Session
from sqlalchemy_solr import defaults


class SolrSpec:

_spec = None

def __init__(self, solr_base_url):
def __init__(self, url):
"""
Initializes a SolrSpec object
:param url: Solr base url which can be a string HTTP(S) URL or a sqlalchemy.engine.url.URL.
"""

session = Session()

if isinstance(url, str):
base_url = url
else:
if "verify_ssl" in url.query and url.query["verify_ssl"] in [
"False",
"false",
]:
session.verify = False

token = None
if "token" in url.query:
token = url.query["token"]

if token is not None:
session.headers.update({"Authorization": f"Bearer {token}"})
else:
session.auth = (url.username, url.password)

proto = "http"
if "use_ssl" in url.query and url.query["use_ssl"] in ["True", "true"]:
proto = "https"

server_path = url.database.split("/")[0]

port = url.port or defaults.PORT
base_url = f"{proto}://{url.host}:{port}/{server_path}"

sys_info_response = session.get(
solr_base_url + "/admin/info/system", params={"wt": "json"}
base_url + "/admin/info/system", params={"wt": "json"}
)

spec_version = sys_info_response.json()["lucene"]["solr-spec-version"]
self._spec = list(map(int, spec_version.split(".")))

Expand Down
16 changes: 11 additions & 5 deletions src/sqlalchemy_solr/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
from sqlalchemy.sql import expression
from sqlalchemy.sql import operators
from sqlalchemy.sql.expression import BindParameter
from sqlalchemy_solr import release_flags

from . import solrdbapi as module
from .solr_type_compiler import SolrTypeCompiler
from .solrdbapi import Connection
from .type_map import metadata_type_map

logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.ERROR)
Expand All @@ -42,8 +42,6 @@
class SolrCompiler(compiler.SQLCompiler):
# pylint: disable=abstract-method

SOLR_DATE_RANGE_TRANS_RELEASE = 9

merge_ops = (operators.ge, operators.gt, operators.le, operators.lt)
bounds = {
operators.ge: "[",
Expand All @@ -70,7 +68,10 @@ def visit_binary(
):

# Handled in Solr 9
if Connection.solr_spec.spec()[0] >= self.SOLR_DATE_RANGE_TRANS_RELEASE:
if (
SolrDialect.solr_spec.spec()[0]
>= release_flags.SOLR_DATE_RANGE_TRANS_RELEASE
):
return super().visit_binary(binary, override_operator, eager_grouping, **kw)

if binary.operator not in self.merge_ops:
Expand Down Expand Up @@ -157,7 +158,10 @@ def visit_binary(

def visit_clauselist(self, clauselist, **kw):
# Handled in Solr 9
if Connection.solr_spec.spec()[0] >= self.SOLR_DATE_RANGE_TRANS_RELEASE:
if (
SolrDialect.solr_spec.spec()[0]
>= release_flags.SOLR_DATE_RANGE_TRANS_RELEASE
):
return super().visit_clauselist(clauselist, **kw)

if clauselist.operator == operators.and_:
Expand Down Expand Up @@ -537,6 +541,8 @@ class SolrDialect(default.DefaultDialect):
supports_native_boolean = True
supports_statement_cache = True

solr_spec = None

def __init__(self, **kw):
default.DefaultDialect.__init__(self, **kw)
self.supported_extensions = []
Expand Down
1 change: 1 addition & 0 deletions src/sqlalchemy_solr/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PORT = 8983
30 changes: 24 additions & 6 deletions src/sqlalchemy_solr/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

from requests import RequestException
from requests import Session
from sqlalchemy_solr import defaults
from sqlalchemy_solr import release_flags
from sqlalchemy_solr.admin.solr_spec import SolrSpec
from sqlalchemy_solr.solrdbapi.api_exceptions import DatabaseError

from .api_globals import _HEADER
Expand Down Expand Up @@ -86,7 +89,7 @@ def create_connect_args(self, url):

# Save this for later use.
self.host = url.host
self.port = url_port
self.port = url.port or defaults.PORT
self.username = url.username
self.password = url.password
self.db = db
Expand All @@ -96,11 +99,7 @@ def create_connect_args(self, url):
# Prepare a session with proper authorization handling.
session = Session()
# session.verify property which is bydefault true so Handled here
if "verify_ssl" in url.query and url.query["verify_ssl"] in [
False,
"False",
"false",
]:
if "verify_ssl" in url.query and url.query["verify_ssl"] in ["False", "false"]:
session.verify = False

if self.token is not None:
Expand Down Expand Up @@ -209,3 +208,22 @@ def get_unique_columns(self, columns):
columns_set.remove(c["name"])

return unique_columns

def on_connect_url(self, url):
SolrDialect.solr_spec = SolrSpec(url)

def do_on_connect(connection): # pylint: disable=unused-argument
SolrDialect.solr_spec = SolrSpec(url)

if (
SolrDialect.solr_spec.spec()[0]
< release_flags.SOLR_DATE_RANGE_TRANS_RELEASE
):
logging.warning(
"Solr version %s less than 9, SQL compilation cache disabled",
SolrDialect.solr_spec.spec()[0],
)
SolrDialect_http.supports_statement_cache = False
SolrDialect.supports_statement_cache = False

return do_on_connect
1 change: 1 addition & 0 deletions src/sqlalchemy_solr/release_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SOLR_DATE_RANGE_TRANS_RELEASE = 9
19 changes: 8 additions & 11 deletions src/sqlalchemy_solr/solrdbapi/_solrdbapi.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import logging

from requests import Session
from sqlalchemy_solr import defaults

from .. import type_map
from ..admin.solr_spec import SolrSpec
from ..api_globals import _HEADER
from ..api_globals import _PAYLOAD
from ..message_formatter import MessageFormatter
Expand Down Expand Up @@ -286,7 +286,6 @@ def __iter__(self):
class Connection:
# pylint: disable=too-many-instance-attributes

solr_spec = None
mf = MessageFormatter()

# pylint: disable=too-many-arguments
Expand Down Expand Up @@ -314,8 +313,6 @@ def __init__(
self._session = session
self._connected = True

Connection.solr_spec = SolrSpec(f"{proto}{host}:{port}/{server_path}")

SolrTableReflection.connection = self

@property
Expand Down Expand Up @@ -374,23 +371,23 @@ def cursor(self):
# pylint: disable=too-many-arguments
def connect(
host,
port=8047,
db=None,
db,
server_path,
collection,
port=defaults.PORT,
username=None,
password=None,
server_path="solr",
collection=None,
use_ssl=False,
use_ssl=None,
verify_ssl=None,
token=None,
):

session = Session()
# bydefault session.verify is set to True
if verify_ssl is not None and verify_ssl in [False, "False", "false"]:
if verify_ssl is not None and verify_ssl in ["False", "false"]:
session.verify = False

if use_ssl in [True, "True", "true"]:
if use_ssl in ["True", "true"]:
proto = "https://"
else:
proto = "http://"
Expand Down
10 changes: 10 additions & 0 deletions tests/assertions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import pytest
from sqlalchemy_solr.admin.solr_spec import SolrSpec


def assert_solr_release(settings, releases):
solr_spec = SolrSpec(settings["SOLR_BASE_URL"])
if solr_spec.spec()[0] not in releases:
pytest.skip(
reason=f"Solr spec version {solr_spec} not compatible with the current test"
)
58 changes: 0 additions & 58 deletions tests/test_sql_compilation_caching.py

This file was deleted.

54 changes: 54 additions & 0 deletions tests/test_sql_compilation_caching_6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from sqlalchemy import select
from sqlalchemy.sql.expression import bindparam
from sqlalchemy.util.langhelpers import _symbol
from tests import assertions
from tests.setup import prepare_orm

releases = [6, 7, 8]


class TestSQLCompilationCaching:

def test_sql_compilation_caching_1(self, settings):
assertions.assert_solr_release(settings, releases)

engine, t = prepare_orm(settings)

qry_1 = (select(t.c.COUNTRY_s).select_from(t)).limit(1)
qry_2 = (select(t.c.COUNTRY_s).select_from(t)).limit(10)

with engine.connect() as connection:
result_1 = connection.execute(qry_1)
result_2 = connection.execute(qry_2)

assert result_1.context.cache_hit == _symbol("NO_DIALECT_SUPPORT")
assert result_2.context.cache_hit == _symbol("NO_DIALECT_SUPPORT")

def test_sql_compilation_caching_2(self, settings):
assertions.assert_solr_release(settings, releases)

engine, t = prepare_orm(settings)

qry_1 = (select(t.c.COUNTRY_s).select_from(t)).limit(1).offset(1)
qry_2 = (select(t.c.COUNTRY_s).select_from(t)).limit(1).offset(2)

with engine.connect() as connection:
result_1 = connection.execute(qry_1)
result_2 = connection.execute(qry_2)

assert result_1.context.cache_hit == _symbol("NO_DIALECT_SUPPORT")
assert result_2.context.cache_hit == _symbol("NO_DIALECT_SUPPORT")

def test_sql_compilation_caching_3(self, settings):
assertions.assert_solr_release(settings, releases)

engine, t = prepare_orm(settings)

qry = select(t).where(t.c.COUNTRY_s == bindparam("COUNTRY_s")).limit(10)

with engine.connect() as connection:
result_1 = connection.execute(qry, {"COUNTRY_s": "Sweden"})
result_2 = connection.execute(qry, {"COUNTRY_s": "France"})

assert result_1.context.cache_hit == _symbol("NO_DIALECT_SUPPORT")
assert result_2.context.cache_hit == _symbol("NO_DIALECT_SUPPORT")
Loading

0 comments on commit 091dc7c

Please sign in to comment.