relationalai 0.11.2__py3-none-any.whl → 0.11.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. relationalai/clients/snowflake.py +44 -15
  2. relationalai/clients/types.py +1 -0
  3. relationalai/clients/use_index_poller.py +446 -178
  4. relationalai/early_access/builder/std/__init__.py +1 -1
  5. relationalai/early_access/dsl/bindings/csv.py +4 -4
  6. relationalai/semantics/internal/internal.py +22 -4
  7. relationalai/semantics/lqp/executor.py +69 -18
  8. relationalai/semantics/lqp/intrinsics.py +23 -0
  9. relationalai/semantics/lqp/model2lqp.py +16 -6
  10. relationalai/semantics/lqp/passes.py +3 -4
  11. relationalai/semantics/lqp/primitives.py +38 -14
  12. relationalai/semantics/metamodel/builtins.py +152 -11
  13. relationalai/semantics/metamodel/factory.py +3 -2
  14. relationalai/semantics/metamodel/helpers.py +78 -2
  15. relationalai/semantics/reasoners/graph/core.py +343 -40
  16. relationalai/semantics/reasoners/optimization/solvers_dev.py +20 -1
  17. relationalai/semantics/reasoners/optimization/solvers_pb.py +24 -3
  18. relationalai/semantics/rel/compiler.py +5 -17
  19. relationalai/semantics/rel/executor.py +2 -2
  20. relationalai/semantics/rel/rel.py +6 -0
  21. relationalai/semantics/rel/rel_utils.py +37 -1
  22. relationalai/semantics/rel/rewrite/extract_common.py +153 -242
  23. relationalai/semantics/sql/compiler.py +540 -202
  24. relationalai/semantics/sql/executor/duck_db.py +21 -0
  25. relationalai/semantics/sql/executor/result_helpers.py +7 -0
  26. relationalai/semantics/sql/executor/snowflake.py +9 -2
  27. relationalai/semantics/sql/rewrite/denormalize.py +4 -6
  28. relationalai/semantics/sql/rewrite/recursive_union.py +23 -3
  29. relationalai/semantics/sql/sql.py +120 -46
  30. relationalai/semantics/std/__init__.py +9 -4
  31. relationalai/semantics/std/datetime.py +363 -0
  32. relationalai/semantics/std/math.py +77 -0
  33. relationalai/semantics/std/re.py +83 -0
  34. relationalai/semantics/std/strings.py +1 -1
  35. relationalai/tools/cli_controls.py +445 -60
  36. relationalai/util/format.py +78 -1
  37. {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/METADATA +3 -2
  38. {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/RECORD +41 -39
  39. relationalai/semantics/std/dates.py +0 -213
  40. {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/WHEEL +0 -0
  41. {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/entry_points.txt +0 -0
  42. {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/licenses/LICENSE +0 -0
@@ -2048,7 +2048,7 @@ Otherwise, remove it from your '{profile}' configuration profile.
2048
2048
  assert len(parsed) == 4, f"Invalid source: {source}"
2049
2049
  db, schema, entity, identity = parsed
2050
2050
  assert db and schema and entity and identity, f"Invalid source: {source}"
2051
- source_types[identity] = cast(SourceInfo, {"type": None, "state": ""})
2051
+ source_types[identity] = cast(SourceInfo, {"type": None, "state": "", "columns_hash": None})
2052
2052
  partitioned_sources[db][schema].append(entity)
2053
2053
 
2054
2054
  # TODO: Move to NA layer
@@ -2057,6 +2057,7 @@ Otherwise, remove it from your '{profile}' configuration profile.
2057
2057
  f"""SELECT
2058
2058
  inf.FQN,
2059
2059
  inf.KIND,
2060
+ inf.COLUMNS_HASH,
2060
2061
  IFF(DATEDIFF(second, ds.created_at::TIMESTAMP, inf.LAST_DDL::TIMESTAMP) > 0, 'STALE', 'CURRENT') AS STATE
2061
2062
  FROM (
2062
2063
  SELECT (SELECT {app_name}.api.normalize_fq_ids(ARRAY_CONSTRUCT(FQ_OBJECT_NAME))[0]:identifier::string) as FQ_OBJECT_NAME,
@@ -2068,26 +2069,45 @@ Otherwise, remove it from your '{profile}' configuration profile.
2068
2069
  (SELECT {app_name}.api.normalize_fq_ids(
2069
2070
  ARRAY_CONSTRUCT(
2070
2071
  CASE
2071
- WHEN TABLE_CATALOG = UPPER(TABLE_CATALOG) THEN TABLE_CATALOG
2072
- ELSE '"' || TABLE_CATALOG || '"'
2072
+ WHEN t.TABLE_CATALOG = UPPER(t.TABLE_CATALOG) THEN t.TABLE_CATALOG
2073
+ ELSE '"' || t.TABLE_CATALOG || '"'
2073
2074
  END || '.' ||
2074
2075
  CASE
2075
- WHEN TABLE_SCHEMA = UPPER(TABLE_SCHEMA) THEN TABLE_SCHEMA
2076
- ELSE '"' || TABLE_SCHEMA || '"'
2076
+ WHEN t.TABLE_SCHEMA = UPPER(t.TABLE_SCHEMA) THEN t.TABLE_SCHEMA
2077
+ ELSE '"' || t.TABLE_SCHEMA || '"'
2077
2078
  END || '.' ||
2078
2079
  CASE
2079
- WHEN TABLE_NAME = UPPER(TABLE_NAME) THEN TABLE_NAME
2080
- ELSE '"' || TABLE_NAME || '"'
2080
+ WHEN t.TABLE_NAME = UPPER(t.TABLE_NAME) THEN t.TABLE_NAME
2081
+ ELSE '"' || t.TABLE_NAME || '"'
2081
2082
  END
2082
2083
  )
2083
2084
  )[0]:identifier::string) as FQN,
2084
2085
  CONVERT_TIMEZONE('UTC', LAST_DDL) AS LAST_DDL,
2085
- TABLE_TYPE as KIND
2086
- FROM {db}.INFORMATION_SCHEMA.TABLES
2087
- WHERE TABLE_CATALOG = {IdentityParser.to_sql_value(db)} AND ({" OR ".join(
2088
- f"(TABLE_SCHEMA = {IdentityParser.to_sql_value(schema)} AND TABLE_NAME IN ({','.join(f'{IdentityParser.to_sql_value(table)}' for table in tables)}))"
2086
+ TABLE_TYPE as KIND,
2087
+ SHA2(LISTAGG(
2088
+ COLUMN_NAME ||
2089
+ CASE
2090
+ WHEN c.NUMERIC_PRECISION IS NOT NULL AND c.NUMERIC_SCALE IS NOT NULL
2091
+ THEN c.DATA_TYPE || '(' || c.NUMERIC_PRECISION || ',' || c.NUMERIC_SCALE || ')'
2092
+ WHEN c.DATETIME_PRECISION IS NOT NULL
2093
+ THEN c.DATA_TYPE || '(0,' || c.DATETIME_PRECISION || ')'
2094
+ WHEN c.CHARACTER_MAXIMUM_LENGTH IS NOT NULL
2095
+ THEN c.DATA_TYPE || '(' || c.CHARACTER_MAXIMUM_LENGTH || ')'
2096
+ ELSE c.DATA_TYPE
2097
+ END ||
2098
+ IS_NULLABLE,
2099
+ ','
2100
+ ) WITHIN GROUP (ORDER BY COLUMN_NAME), 256) as COLUMNS_HASH
2101
+ FROM {db}.INFORMATION_SCHEMA.TABLES t
2102
+ JOIN {db}.INFORMATION_SCHEMA.COLUMNS c
2103
+ ON t.TABLE_CATALOG = c.TABLE_CATALOG
2104
+ AND t.TABLE_SCHEMA = c.TABLE_SCHEMA
2105
+ AND t.TABLE_NAME = c.TABLE_NAME
2106
+ WHERE t.TABLE_CATALOG = {IdentityParser.to_sql_value(db)} AND ({" OR ".join(
2107
+ f"(t.TABLE_SCHEMA = {IdentityParser.to_sql_value(schema)} AND t.TABLE_NAME IN ({','.join(f'{IdentityParser.to_sql_value(table)}' for table in tables)}))"
2089
2108
  for schema, tables in schemas.items()
2090
2109
  )})
2110
+ GROUP BY t.TABLE_CATALOG, t.TABLE_SCHEMA, t.TABLE_NAME, t.LAST_DDL, t.TABLE_TYPE
2091
2111
  ) inf on inf.FQN = ds.FQ_OBJECT_NAME
2092
2112
  """
2093
2113
  for db, schemas in partitioned_sources.items()
@@ -2102,6 +2122,7 @@ Otherwise, remove it from your '{profile}' configuration profile.
2102
2122
  assert fqn, f"Error parsing returned FQN: {row_fqn}"
2103
2123
 
2104
2124
  source_types[fqn]["type"] = "TABLE" if row["KIND"] == "BASE TABLE" else row["KIND"]
2125
+ source_types[fqn]["columns_hash"] = row["COLUMNS_HASH"]
2105
2126
  source_types[fqn]["state"] = row["STATE"]
2106
2127
 
2107
2128
  return source_types
@@ -3058,10 +3079,13 @@ class DirectAccessResources(Resources):
3058
3079
  return self._retrieve_service_endpoint()
3059
3080
 
3060
3081
  def _retrieve_service_endpoint(self, enforce_update=False) -> str:
3082
+ account = self.config.get("account")
3083
+ app_name = self.config.get("rai_app_name")
3084
+ service_endpoint_key = f"{account}.{app_name}.service_endpoint"
3061
3085
  if self._service_endpoint and not enforce_update:
3062
3086
  return self._service_endpoint
3063
- if self._endpoint_info.get("service_endpoint", "") and not enforce_update:
3064
- self._service_endpoint = str(self._endpoint_info.get("service_endpoint", ""))
3087
+ if self._endpoint_info.get(service_endpoint_key, "") and not enforce_update:
3088
+ self._service_endpoint = str(self._endpoint_info.get(service_endpoint_key, ""))
3065
3089
  return self._service_endpoint
3066
3090
 
3067
3091
  is_snowflake_notebook = isinstance(runtime_env, SnowbookEnvironment)
@@ -3073,7 +3097,7 @@ class DirectAccessResources(Resources):
3073
3097
  else:
3074
3098
  self._service_endpoint = f"https://{result[0]['SERVICE_ENDPOINT']}"
3075
3099
 
3076
- self._endpoint_info.set("service_endpoint", self._service_endpoint)
3100
+ self._endpoint_info.set(service_endpoint_key, self._service_endpoint)
3077
3101
  # save the endpoint to `ENDPOINT_FILE` to avoid calling the endpoint with every
3078
3102
  # pyrel execution
3079
3103
  try:
@@ -3116,7 +3140,12 @@ class DirectAccessResources(Resources):
3116
3140
  try:
3117
3141
  response = _send_request()
3118
3142
  if response.status_code != 200:
3119
- message = response.json().get("message", "")
3143
+ try:
3144
+ message = response.json().get("message", "")
3145
+ except requests.exceptions.JSONDecodeError:
3146
+ raise ResponseStatusException(
3147
+ f"Failed to parse error response from endpoint {endpoint}.", response
3148
+ )
3120
3149
 
3121
3150
  # fix engine on engine error and retry
3122
3151
  if _is_engine_issue(message):
@@ -41,6 +41,7 @@ class EngineState(TypedDict):
41
41
  class SourceInfo(TypedDict):
42
42
  type: str|None
43
43
  state: str
44
+ columns_hash: str|None
44
45
  source: str
45
46
 
46
47