tinybird 0.0.1.dev178__py3-none-any.whl → 0.0.1.dev180__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.

Potentially problematic release.


This version of tinybird might be problematic. Click here for more details.

tinybird/sql_toolset.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import copy
2
2
  import logging
3
+ import re
3
4
  from collections import defaultdict
4
5
  from datetime import datetime
5
6
  from functools import lru_cache
@@ -42,10 +43,88 @@ class InvalidResource(ValueError):
42
43
  self.table = table
43
44
 
44
45
 
46
+ class UnoptimizedJoinException(Exception):
47
+ def __init__(self, sql: str):
48
+ self.sql = sql
49
+ self.msg = f"Materialized node SQL contains a join that is not optimized: {sql}"
50
+ self.documentation = (
51
+ "/docs/work-with-data/optimization/opt201-fix-mistakes#understanding-the-materialized-join-issue"
52
+ )
53
+ super().__init__(self.msg)
54
+
55
+
56
+ ChQueryTable = Tuple[Optional[str], Optional[str], Optional[str]]
57
+
58
+
59
+ def get_left_table(sql: str, default_database: Optional[str] = None) -> ChQueryTable:
60
+ if default_database is None:
61
+ left_table = chquery.get_left_table(sql)
62
+ else:
63
+ left_table = chquery.get_left_table(sql, default_database=default_database)
64
+ return left_table
65
+
66
+
45
67
  def format_sql(sql: str) -> str:
46
68
  return chquery.format(sql)
47
69
 
48
70
 
71
+ def explain_plan(sql: str) -> str:
72
+ return chquery.explain_ast(sql)
73
+
74
+
75
+ def has_join(sql: str) -> bool:
76
+ return any(line.rstrip().startswith("TableJoin") for line in explain_plan(sql).split())
77
+
78
+
79
+ def has_unoptimized_join(sql: str, left_table: Optional[Union[Tuple[str, str], Tuple[str, str, str]]] = None) -> None:
80
+ """
81
+ Check if a SQL query contains an unoptimized join.
82
+ A join is considered optimized if the right table is filtered by the left table's data.
83
+
84
+ Args:
85
+ sql: The SQL query to check
86
+ left_table: Optional tuple of (database, table) for the left table
87
+
88
+ Raises:
89
+ UnoptimizedJoin: If an unoptimized join is found
90
+ """
91
+ # TODO: We should check that we are filtering the right table by the left table's data
92
+ # TODO: We should check if using EXPLAIN AST is better than using regex
93
+
94
+ number_of_joins = sum(1 for line in explain_plan(sql).split() if line.rstrip().startswith("TableJoin"))
95
+ if number_of_joins == 0:
96
+ return
97
+
98
+ if not left_table:
99
+ left_table = chquery.get_left_table(sql)
100
+ if not left_table:
101
+ return
102
+
103
+ # Find all JOIN clauses with subqueries
104
+ # This pattern matches anything between JOIN and ON/USING
105
+ join_pattern = r"(?:LEFT\s+|RIGHT\s+|INNER\s+|FULL\s+OUTER\s+)?JOIN\s*\((.*?)\)\s+(?:AS\s+\w+)?\s*(?:ON|USING)"
106
+
107
+ # Find all joins with subqueries
108
+ join_matches = list(re.finditer(join_pattern, sql, re.IGNORECASE | re.DOTALL))
109
+
110
+ if number_of_joins != len(join_matches):
111
+ logging.debug(f"number_of_joins: {number_of_joins}, join_matches: {join_matches}")
112
+ raise UnoptimizedJoinException(sql)
113
+
114
+ # If no joins with subqueries found, probably is an unoptimized join
115
+ if not join_matches:
116
+ raise UnoptimizedJoinException(sql)
117
+
118
+ # Check if the left table is referenced in the subquery
119
+ left_table_ref = f"{left_table[0]}.{left_table[1]}"
120
+
121
+ for match in join_matches:
122
+ subquery = match.group(1) # Get the captured subquery
123
+ logging.debug(f"subquery: {subquery} left_table_ref: {left_table_ref}")
124
+ if left_table_ref not in subquery:
125
+ raise UnoptimizedJoinException(sql)
126
+
127
+
49
128
  def format_where_for_mutation_command(where_clause: str) -> str:
50
129
  """
51
130
  >>> format_where_for_mutation_command("numnights = 99")
tinybird/tb/__cli__.py CHANGED
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/forward/commands'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '0.0.1.dev178'
8
- __revision__ = 'c2632b6'
7
+ __version__ = '0.0.1.dev180'
8
+ __revision__ = '4ea1a7e'
@@ -89,7 +89,6 @@ def build_project(project: Project, tb_client: TinyB, silent: bool = False) -> O
89
89
  files = [
90
90
  ("context://", ("cli-version", "1.0.0", "text/plain")),
91
91
  ]
92
- fds = []
93
92
  project_path = project.path
94
93
  project_files = project.get_project_files()
95
94
 
@@ -98,10 +97,11 @@ def build_project(project: Project, tb_client: TinyB, silent: bool = False) -> O
98
97
 
99
98
  for file_path in project_files:
100
99
  relative_path = str(Path(file_path).relative_to(project_path))
101
- fd = open(file_path, "rb")
102
- fds.append(fd)
103
- content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
104
- files.append((MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd.read().decode("utf-8"), content_type)))
100
+ with open(file_path, "rb") as fd:
101
+ content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
102
+ files.append(
103
+ (MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd.read().decode("utf-8"), content_type))
104
+ )
105
105
  HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
106
106
 
107
107
  r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS)
@@ -195,9 +195,6 @@ def build_project(project: Project, tb_client: TinyB, silent: bool = False) -> O
195
195
  error = f"Unknown build result. Error: {result.get('error')}"
196
196
  except Exception as e:
197
197
  error = str(e)
198
- finally:
199
- for fd in fds:
200
- fd.close()
201
198
 
202
199
  if error:
203
200
  raise click.ClickException(error)
@@ -524,13 +524,11 @@ def create_deployment(
524
524
  files = [
525
525
  ("context://", ("cli-version", "1.0.0", "text/plain")),
526
526
  ]
527
- fds = []
528
527
  for file_path in project.get_project_files():
529
528
  relative_path = str(Path(file_path).relative_to(project.path))
530
- fd = open(file_path, "rb")
531
- fds.append(fd)
532
- content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
533
- files.append((MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd.read().decode("utf-8"), content_type)))
529
+ with open(file_path, "rb") as fd:
530
+ content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
531
+ files.append((MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd.read().decode("utf-8"), content_type)))
534
532
 
535
533
  deployment = None
536
534
  try:
@@ -605,9 +603,6 @@ def create_deployment(
605
603
  click.echo(FeedbackManager.error(message=f"Unknown deployment result {status}"))
606
604
  except Exception as e:
607
605
  click.echo(FeedbackManager.error_exception(error=e))
608
- finally:
609
- for fd in fds:
610
- fd.close()
611
606
 
612
607
  if not deployment and not check:
613
608
  sys_exit("deployment_error", "Deployment failed")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev178
3
+ Version: 0.0.1.dev180
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -7,17 +7,17 @@ tinybird/prompts.py,sha256=uFzlVsGgWFwEOBhXkaLL4zY-wz8jCQgTFiMEfoFcgro,36550
7
7
  tinybird/sql.py,sha256=C_B81wwv3BsqyXGhF5oTk9DcTUkrp7NwIFqSzd3Dmjc,47854
8
8
  tinybird/sql_template.py,sha256=hWW8JawSWLl9GeWPYkC_Yrxj7P0MHEVMJ0Px9bedEgM,99817
9
9
  tinybird/sql_template_fmt.py,sha256=KUHdj5rYCYm_rKKdXYSJAE9vIyXUQLB0YSZnUXHeBlY,10196
10
- tinybird/sql_toolset.py,sha256=KORVbNAUTfW1qo3U9oe7Z59xQ0QMsFhB0ji3HzY2JVo,15324
10
+ tinybird/sql_toolset.py,sha256=eGYzhuxg9SGXdevV4xJMI7X7jlc_UzK1w84t4aRZFZU,18230
11
11
  tinybird/syncasync.py,sha256=IPnOx6lMbf9SNddN1eBtssg8vCLHMt76SuZ6YNYm-Yk,27761
12
12
  tinybird/tornado_template.py,sha256=jjNVDMnkYFWXflmT8KU_Ssbo5vR8KQq3EJMk5vYgXRw,41959
13
13
  tinybird/ch_utils/constants.py,sha256=aYvg2C_WxYWsnqPdZB1ZFoIr8ZY-XjUXYyHKE9Ansj0,3890
14
14
  tinybird/ch_utils/engine.py,sha256=X4tE9OrfaUy6kO9cqVEzyI9cDcmOF3IAssRRzsTsfEQ,40781
15
- tinybird/tb/__cli__.py,sha256=nsmL84fKdwG5x6nXbynOK9RY9QmmKkjzGOxJQg6xFsA,247
15
+ tinybird/tb/__cli__.py,sha256=Kac5sOlejovZ8ZTdL4H0Omd9RVtBkFUm2IX9GQBmdG0,247
16
16
  tinybird/tb/check_pypi.py,sha256=rW4QmDRbtgKdUUwJCnBkVjmTjZSZGN-XgZhx7vMkC0w,1009
17
17
  tinybird/tb/cli.py,sha256=u3eGOhX0MHkuT6tiwaZ0_3twqLmqKXDAOxF7yV_Nn9Q,1075
18
18
  tinybird/tb/client.py,sha256=CSBl_JRuioPyY0H8Ac96dJ9wQXDXfrvK2lwqlOxKGoY,55715
19
19
  tinybird/tb/config.py,sha256=jT9xndpeCY_g0HdB5qE2EquC0TFRRnkPnQFWZWd04jo,3998
20
- tinybird/tb/modules/build.py,sha256=zakH5812Lop-XHjGmDRdOPeofPtoeyb_2un_T6e50xw,19177
20
+ tinybird/tb/modules/build.py,sha256=rRL5XKBdadMc9uVDEUt0GXm0h09Y6XXw199rdmRI1qo,19127
21
21
  tinybird/tb/modules/cicd.py,sha256=MnShTTJzKBYeajswF2jg7p7ZzupaeCgSriAN05MeEdg,7330
22
22
  tinybird/tb/modules/cli.py,sha256=dXZs-MuqYPvxStVj7aLg36LwXtEB8NzTobDmHV9nzZI,15508
23
23
  tinybird/tb/modules/common.py,sha256=lu1Z3VYImwocaHvqOW2FzBJP6F3Ev7RuXsItkCZ6jpo,83237
@@ -26,7 +26,7 @@ tinybird/tb/modules/connection.py,sha256=7oOR7x4PhBcm1ETFFCH2YJ_3oeGXjAbmx1cnZX9
26
26
  tinybird/tb/modules/copy.py,sha256=2Mm4FWKehOG7CoOhiF1m9UZJgJn0W1_cMolqju8ONYg,5805
27
27
  tinybird/tb/modules/create.py,sha256=jz5jswVntvsnNN-IbCXoKw9pUKySx_IubpP0rxi7Iy0,17528
28
28
  tinybird/tb/modules/datasource.py,sha256=mZ9N9LxbNA9QoKSMbxcMiQkdaQkfFqLeFB9_88e3_68,17843
29
- tinybird/tb/modules/deployment.py,sha256=hbQ5ijbAp8Rvj4MRA7X8538Sb6Eko6MbD8jJ0MVsiTI,27227
29
+ tinybird/tb/modules/deployment.py,sha256=t6DDLJ1YdY3SJiTPbEG7CRblSLkbuqwzauQ9y65FWtY,27147
30
30
  tinybird/tb/modules/deprecations.py,sha256=rrszC1f_JJeJ8mUxGoCxckQTJFBCR8wREf4XXXN-PRc,4507
31
31
  tinybird/tb/modules/dev_server.py,sha256=57FCKuWpErwYUYgHspYDkLWEm9F4pbvVOtMrFXX1fVU,10129
32
32
  tinybird/tb/modules/endpoint.py,sha256=XySDt3pk66vxOZ0egUfz4bY8bEk3BjOXkv-L0OIJ3sc,12083
@@ -80,8 +80,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
80
80
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
81
81
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
82
82
  tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
83
- tinybird-0.0.1.dev178.dist-info/METADATA,sha256=2Nf3nXHYpiPTEE5DzWyymJ1AatyVhDeThpo5zQgZXPA,1608
84
- tinybird-0.0.1.dev178.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
85
- tinybird-0.0.1.dev178.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
86
- tinybird-0.0.1.dev178.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
87
- tinybird-0.0.1.dev178.dist-info/RECORD,,
83
+ tinybird-0.0.1.dev180.dist-info/METADATA,sha256=CQzcZE6kic8iU_phMFMVuaohz8lPb9-JbFgjQ61BUDw,1608
84
+ tinybird-0.0.1.dev180.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
85
+ tinybird-0.0.1.dev180.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
86
+ tinybird-0.0.1.dev180.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
87
+ tinybird-0.0.1.dev180.dist-info/RECORD,,