databricks-labs-lakebridge 0.10.1__py3-none-any.whl → 0.10.3__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 (22) hide show
  1. databricks/labs/lakebridge/__about__.py +1 -1
  2. databricks/labs/lakebridge/cli.py +374 -179
  3. databricks/labs/lakebridge/config.py +3 -5
  4. databricks/labs/lakebridge/helpers/file_utils.py +10 -9
  5. databricks/labs/lakebridge/helpers/string_utils.py +0 -34
  6. databricks/labs/lakebridge/install.py +47 -19
  7. databricks/labs/lakebridge/intermediate/root_tables.py +5 -7
  8. databricks/labs/lakebridge/reconcile/connectors/source_adapter.py +2 -2
  9. databricks/labs/lakebridge/reconcile/connectors/{sql_server.py → tsql.py} +1 -1
  10. databricks/labs/lakebridge/reconcile/constants.py +1 -0
  11. databricks/labs/lakebridge/transpiler/execute.py +10 -8
  12. databricks/labs/lakebridge/transpiler/lsp/lsp_engine.py +13 -8
  13. databricks/labs/lakebridge/transpiler/sqlglot/sqlglot_engine.py +4 -0
  14. databricks/labs/lakebridge/transpiler/transpile_engine.py +4 -6
  15. {databricks_labs_lakebridge-0.10.1.dist-info → databricks_labs_lakebridge-0.10.3.dist-info}/METADATA +3 -3
  16. {databricks_labs_lakebridge-0.10.1.dist-info → databricks_labs_lakebridge-0.10.3.dist-info}/RECORD +22 -20
  17. docs/lakebridge/src/components/ReconcileTabs.tsx +86 -0
  18. docs/lakebridge/src/theme/DocSidebarItems/index.tsx +42 -0
  19. {databricks_labs_lakebridge-0.10.1.dist-info → databricks_labs_lakebridge-0.10.3.dist-info}/WHEEL +0 -0
  20. {databricks_labs_lakebridge-0.10.1.dist-info → databricks_labs_lakebridge-0.10.3.dist-info}/entry_points.txt +0 -0
  21. {databricks_labs_lakebridge-0.10.1.dist-info → databricks_labs_lakebridge-0.10.3.dist-info}/licenses/LICENSE +0 -0
  22. {databricks_labs_lakebridge-0.10.1.dist-info → databricks_labs_lakebridge-0.10.3.dist-info}/licenses/NOTICE +0 -0
@@ -1,5 +1,3 @@
1
- from __future__ import annotations
2
-
3
1
  import logging
4
2
  from dataclasses import dataclass
5
3
  from enum import Enum, auto
@@ -31,11 +29,11 @@ class LSPConfigOptionV1:
31
29
  default: Any = None
32
30
 
33
31
  @classmethod
34
- def parse_all(cls, data: dict[str, Any]) -> dict[str, list[LSPConfigOptionV1]]:
32
+ def parse_all(cls, data: dict[str, Any]) -> dict[str, list["LSPConfigOptionV1"]]:
35
33
  return {key: list(LSPConfigOptionV1.parse(item) for item in value) for (key, value) in data.items()}
36
34
 
37
35
  @classmethod
38
- def parse(cls, data: Any) -> LSPConfigOptionV1:
36
+ def parse(cls, data: Any) -> "LSPConfigOptionV1":
39
37
  if not isinstance(data, dict):
40
38
  raise ValueError(f"Invalid transpiler config option, expecting a dict entry, got {data}")
41
39
  flag: str = data.get("flag", "")
@@ -79,7 +77,7 @@ class TranspileConfig:
79
77
  output_folder: str | None = None
80
78
  error_file_path: str | None = None
81
79
  sdk_config: dict[str, str] | None = None
82
- skip_validation: bool | None = False
80
+ skip_validation: bool = False
83
81
  catalog_name: str = "remorph"
84
82
  schema_name: str = "transpiler"
85
83
  transpiler_options: JsonValue = None
@@ -1,3 +1,5 @@
1
+ import contextlib
2
+ import os
1
3
  from pathlib import Path
2
4
  from collections.abc import Generator
3
5
 
@@ -53,12 +55,11 @@ def get_sql_file(input_path: str | Path) -> Generator[Path, None, None]:
53
55
  yield filename
54
56
 
55
57
 
56
- def read_file(filename: str | Path) -> str:
57
- """
58
- Reads the contents of the given file and returns it as a string.
59
- :param filename: Input File Path
60
- :return: File Contents as String
61
- """
62
- # pylint: disable=unspecified-encoding
63
- with Path(filename).open() as file:
64
- return file.read()
58
+ @contextlib.contextmanager
59
+ def chdir(new_path: Path) -> Generator[None, None, None]:
60
+ saved_path = Path.cwd()
61
+ try:
62
+ os.chdir(new_path)
63
+ yield
64
+ finally:
65
+ os.chdir(saved_path)
@@ -1,37 +1,3 @@
1
- import codecs
2
-
3
-
4
- # Optionally check to see if a string begins with a Byte Order Mark
5
- # such a character will cause the transpiler to fail
6
- def remove_bom(input_string: str) -> str:
7
- """
8
- Removes the Byte Order Mark (BOM) from the given string if it exists.
9
- :param input_string: String to remove BOM from
10
- :return: String without BOM
11
- """
12
- output_string = input_string
13
-
14
- # Check and remove UTF-16 (LE and BE) BOM
15
- if input_string.startswith(codecs.BOM_UTF16_BE.decode("utf-16-be")):
16
- output_string = input_string[len(codecs.BOM_UTF16_BE.decode("utf-16-be")) :]
17
- elif input_string.startswith(codecs.BOM_UTF16_LE.decode("utf-16-le")):
18
- output_string = input_string[len(codecs.BOM_UTF16_LE.decode("utf-16-le")) :]
19
- elif input_string.startswith(codecs.BOM_UTF16.decode("utf-16")):
20
- output_string = input_string[len(codecs.BOM_UTF16.decode("utf-16")) :]
21
- # Check and remove UTF-32 (LE and BE) BOM
22
- elif input_string.startswith(codecs.BOM_UTF32_BE.decode("utf-32-be")):
23
- output_string = input_string[len(codecs.BOM_UTF32_BE.decode("utf-32-be")) :]
24
- elif input_string.startswith(codecs.BOM_UTF32_LE.decode("utf-32-le")):
25
- output_string = input_string[len(codecs.BOM_UTF32_LE.decode("utf-32-le")) :]
26
- elif input_string.startswith(codecs.BOM_UTF32.decode("utf-32")):
27
- output_string = input_string[len(codecs.BOM_UTF32.decode("utf-32")) :]
28
- # Check and remove UTF-8 BOM
29
- elif input_string.startswith(codecs.BOM_UTF8.decode("utf-8")):
30
- output_string = input_string[len(codecs.BOM_UTF8.decode("utf-8")) :]
31
-
32
- return output_string
33
-
34
-
35
1
  def refactor_hexadecimal_chars(input_string: str) -> str:
36
2
  """
37
3
  Updates the HexaDecimal characters ( \x1b[\\d+m ) in the given string as below.
@@ -37,6 +37,7 @@ from databricks.labs.lakebridge.config import (
37
37
 
38
38
  from databricks.labs.lakebridge.deployment.configurator import ResourceConfigurator
39
39
  from databricks.labs.lakebridge.deployment.installation import WorkspaceInstallation
40
+ from databricks.labs.lakebridge.helpers.file_utils import chdir
40
41
  from databricks.labs.lakebridge.reconcile.constants import ReconReportType, ReconSourceType
41
42
  from databricks.labs.lakebridge.transpiler.lsp.lsp_engine import LSPConfig
42
43
 
@@ -251,12 +252,8 @@ class WheelInstaller(TranspilerInstaller):
251
252
  return self._post_install(version)
252
253
 
253
254
  def _create_venv(self) -> None:
254
- cwd = os.getcwd()
255
- try:
256
- os.chdir(self._install_path)
255
+ with chdir(self._install_path):
257
256
  self._unsafe_create_venv()
258
- finally:
259
- os.chdir(cwd)
260
257
 
261
258
  def _unsafe_create_venv(self) -> None:
262
259
  # using the venv module doesn't work (maybe it's not possible to create a venv from a venv ?)
@@ -298,16 +295,12 @@ class WheelInstaller(TranspilerInstaller):
298
295
  raise ValueError(f"Could not locate 'site-packages' for {self._venv!s}")
299
296
 
300
297
  def _install_with_pip(self) -> None:
301
- cwd = os.getcwd()
302
- try:
303
- os.chdir(self._install_path)
298
+ with chdir(self._install_path):
304
299
  # the way to call pip from python is highly sensitive to os and source type
305
300
  if self._artifact:
306
301
  self._install_local_artifact()
307
302
  else:
308
303
  self._install_remote_artifact()
309
- finally:
310
- os.chdir(cwd)
311
304
 
312
305
  def _install_local_artifact(self) -> None:
313
306
  pip = self._locate_pip()
@@ -557,10 +550,9 @@ class WorkspaceInstaller:
557
550
 
558
551
  @classmethod
559
552
  def install_morpheus(cls, artifact: Path | None = None):
560
- java_version = cls.get_java_version()
561
- if java_version is None or java_version < (11, 0, 0, 0):
562
- logger.warning(
563
- "This software requires Java 11 or above. Please install Java and re-run 'install-transpile'."
553
+ if not cls.is_java_version_okay():
554
+ logger.error(
555
+ "The morpheus transpiler requires Java 11 or above. Please install Java and re-run 'install-transpile'."
564
556
  )
565
557
  return
566
558
  product_name = "databricks-morph-plugin"
@@ -568,6 +560,26 @@ class WorkspaceInstaller:
568
560
  artifact_id = product_name
569
561
  TranspilerInstaller.install_from_maven(product_name, group_id, artifact_id, artifact)
570
562
 
563
+ @classmethod
564
+ def is_java_version_okay(cls) -> bool:
565
+ detected_java = cls.find_java()
566
+ match detected_java:
567
+ case None:
568
+ logger.warning("No Java executable found in the system PATH.")
569
+ return False
570
+ case (java_executable, None):
571
+ logger.warning(f"Java found, but could not determine the version: {java_executable}.")
572
+ return False
573
+ case (java_executable, bytes(raw_version)):
574
+ logger.warning(f"Java found ({java_executable}), but could not parse the version:\n{raw_version}")
575
+ return False
576
+ case (java_executable, tuple(old_version)) if old_version < (11, 0, 0, 0):
577
+ version_str = ".".join(str(v) for v in old_version)
578
+ logger.warning(f"Java found ({java_executable}), but version {version_str} is too old.")
579
+ return False
580
+ case _:
581
+ return True
582
+
571
583
  @classmethod
572
584
  def install_artifact(cls, artifact: str):
573
585
  path = Path(artifact)
@@ -582,25 +594,41 @@ class WorkspaceInstaller:
582
594
  logger.fatal(f"Cannot install unsupported artifact: {artifact}")
583
595
 
584
596
  @classmethod
585
- def get_java_version(cls) -> tuple[int, int, int, int] | None:
597
+ def find_java(cls) -> tuple[Path, tuple[int, int, int, int] | bytes | None] | None:
598
+ """Locate Java and return its version, as reported by `java -version`.
599
+
600
+ The java executable is currently located by searching the system PATH. Its version is parsed from the output of
601
+ the `java -version` command, which has been standardized since Java 10.
602
+
603
+ Returns:
604
+ a tuple of its path and the version as a tuple of integers (feature, interim, update, patch), if the java
605
+ executable could be located. If the version cannot be parsed, instead the raw version information is
606
+ returned, or `None` as a last resort. When no java executable is found, `None` is returned instead of a
607
+ tuple.
608
+ """
586
609
  # Platform-independent way to reliably locate the java executable.
587
610
  # Reference: https://docs.python.org/3.10/library/subprocess.html#popen-constructor
588
611
  java_executable = shutil.which("java")
589
612
  if java_executable is None:
590
613
  return None
614
+ java_executable_path = Path(java_executable)
615
+ logger.debug(f"Using java executable: {java_executable_path!r}")
591
616
  try:
592
- completed = run([java_executable, "-version"], shell=False, capture_output=True, check=True)
617
+ completed = run([str(java_executable_path), "-version"], shell=False, capture_output=True, check=True)
593
618
  except CalledProcessError as e:
594
619
  logger.debug(
595
620
  f"Failed to run {e.args!r} (exit-code={e.returncode}, stdout={e.stdout!r}, stderr={e.stderr!r})",
596
621
  exc_info=e,
597
622
  )
598
- return None
623
+ return java_executable_path, None
599
624
  # It might not be ascii, but the bits we care about are so this will never fail.
600
- java_version_output = completed.stderr.decode("ascii", errors="ignore")
625
+ raw_output = completed.stderr
626
+ java_version_output = raw_output.decode("ascii", errors="ignore")
601
627
  java_version = cls._parse_java_version(java_version_output)
628
+ if java_version is None:
629
+ return java_executable_path, raw_output.strip()
602
630
  logger.debug(f"Detected java version: {java_version}")
603
- return java_version
631
+ return java_executable_path, java_version
604
632
 
605
633
  # Pattern to match a Java version string, compiled at import time to ensure it's valid.
606
634
  # Ref: https://docs.oracle.com/en/java/javase/11/install/version-string-format.html
@@ -1,11 +1,9 @@
1
1
  import logging
2
2
  from pathlib import Path
3
3
 
4
- from databricks.labs.lakebridge.helpers.file_utils import (
5
- get_sql_file,
6
- is_sql_file,
7
- read_file,
8
- )
4
+ from databricks.labs.blueprint.paths import read_text
5
+
6
+ from databricks.labs.lakebridge.helpers.file_utils import get_sql_file, is_sql_file
9
7
  from databricks.labs.lakebridge.intermediate.dag import DAG
10
8
 
11
9
  from databricks.labs.lakebridge.transpiler.sqlglot.sqlglot_engine import SqlglotEngine
@@ -26,14 +24,14 @@ class RootTableAnalyzer:
26
24
  # when input is sql file then parse the file
27
25
  if is_sql_file(self.input_path):
28
26
  logger.debug(f"Generating Lineage file: {self.input_path}")
29
- sql_content = read_file(self.input_path)
27
+ sql_content = read_text(self.input_path)
30
28
  self._populate_dag(sql_content, self.input_path, dag)
31
29
  return dag # return after processing the file
32
30
 
33
31
  # when the input is a directory
34
32
  for path in get_sql_file(self.input_path):
35
33
  logger.debug(f"Generating Lineage file: {path}")
36
- sql_content = read_file(path)
34
+ sql_content = read_text(path)
37
35
  self._populate_dag(sql_content, path, dag)
38
36
 
39
37
  return dag
@@ -6,7 +6,7 @@ from databricks.labs.lakebridge.reconcile.connectors.data_source import DataSour
6
6
  from databricks.labs.lakebridge.reconcile.connectors.databricks import DatabricksDataSource
7
7
  from databricks.labs.lakebridge.reconcile.connectors.oracle import OracleDataSource
8
8
  from databricks.labs.lakebridge.reconcile.connectors.snowflake import SnowflakeDataSource
9
- from databricks.labs.lakebridge.reconcile.connectors.sql_server import SQLServerDataSource
9
+ from databricks.labs.lakebridge.reconcile.connectors.tsql import TSQLServerDataSource
10
10
  from databricks.labs.lakebridge.transpiler.sqlglot.generator.databricks import Databricks
11
11
  from databricks.labs.lakebridge.transpiler.sqlglot.parsers.oracle import Oracle
12
12
  from databricks.labs.lakebridge.transpiler.sqlglot.parsers.snowflake import Snowflake
@@ -26,5 +26,5 @@ def create_adapter(
26
26
  if isinstance(engine, Databricks):
27
27
  return DatabricksDataSource(engine, spark, ws, secret_scope)
28
28
  if isinstance(engine, TSQL):
29
- return SQLServerDataSource(engine, spark, ws, secret_scope)
29
+ return TSQLServerDataSource(engine, spark, ws, secret_scope)
30
30
  raise ValueError(f"Unsupported source type --> {engine}")
@@ -47,7 +47,7 @@ _SCHEMA_QUERY = """SELECT
47
47
  """
48
48
 
49
49
 
50
- class SQLServerDataSource(DataSource, SecretsMixin, JDBCReaderMixin):
50
+ class TSQLServerDataSource(DataSource, SecretsMixin, JDBCReaderMixin):
51
51
  _DRIVER = "sqlserver"
52
52
 
53
53
  def __init__(
@@ -18,6 +18,7 @@ class ReconSourceType(AutoName):
18
18
  SNOWFLAKE = auto()
19
19
  ORACLE = auto()
20
20
  DATABRICKS = auto()
21
+ TSQL = auto()
21
22
 
22
23
 
23
24
  class ReconReportType(AutoName):
@@ -9,6 +9,7 @@ from typing import cast
9
9
  import itertools
10
10
 
11
11
  from databricks.labs.blueprint.installation import JsonObject
12
+ from databricks.labs.blueprint.paths import read_text
12
13
  from databricks.labs.lakebridge.__about__ import __version__
13
14
  from databricks.labs.lakebridge.config import (
14
15
  TranspileConfig,
@@ -28,7 +29,6 @@ from databricks.labs.lakebridge.transpiler.transpile_status import (
28
29
  ErrorKind,
29
30
  ErrorSeverity,
30
31
  )
31
- from databricks.labs.lakebridge.helpers.string_utils import remove_bom
32
32
  from databricks.labs.lakebridge.helpers.validation import Validator
33
33
  from databricks.labs.lakebridge.transpiler.sqlglot.sqlglot_engine import SqlglotEngine
34
34
  from databricks.sdk import WorkspaceClient
@@ -62,15 +62,14 @@ async def _process_one_file(context: TranspilingContext) -> tuple[int, list[Tran
62
62
  )
63
63
  return 0, [error]
64
64
 
65
- with context.input_path.open("r") as f:
66
- source_code = remove_bom(f.read())
67
- context = dataclasses.replace(context, source_code=source_code)
65
+ source_code = read_text(context.input_path)
66
+ context = dataclasses.replace(context, source_code=source_code)
68
67
 
69
68
  transpile_result = await _transpile(
70
69
  context.transpiler,
71
70
  str(context.config.source_dialect),
72
71
  context.config.target_dialect,
73
- str(context.source_code),
72
+ source_code,
74
73
  context.input_path,
75
74
  )
76
75
 
@@ -81,8 +80,9 @@ async def _process_one_file(context: TranspilingContext) -> tuple[int, list[Tran
81
80
  error_list = list(transpile_result.error_list)
82
81
  context = dataclasses.replace(context, transpiled_code=transpile_result.transpiled_code)
83
82
 
84
- output_path = cast(Path, context.output_path)
85
- output_path.parent.mkdir(parents=True, exist_ok=True)
83
+ output_path = context.output_path
84
+ assert output_path is not None, "Output path must be set in the context"
85
+ output_path.parent.mkdir(exist_ok=True)
86
86
 
87
87
  if _is_combined_result(transpile_result):
88
88
  _process_combined_result(context, error_list)
@@ -158,7 +158,9 @@ def _process_single_result(context: TranspilingContext, error_list: list[Transpi
158
158
 
159
159
  output_path = cast(Path, context.output_path)
160
160
  with output_path.open("w") as w:
161
- w.write(make_header(context.input_path, error_list))
161
+ # The above adds a java-style comment block at the top of the output file
162
+ # This would break .py or .json outputs so we disable it for now.
163
+ # w.write(make_header(context.input_path, error_list))
162
164
  w.write(output_code)
163
165
 
164
166
  logger.info(f"Processed file: {context.input_path} (errors: {len(error_list)})")
@@ -35,7 +35,7 @@ from pygls.lsp.client import BaseLanguageClient
35
35
  from databricks.labs.blueprint.wheels import ProductInfo
36
36
  from databricks.labs.lakebridge.config import LSPConfigOptionV1, TranspileConfig, TranspileResult
37
37
  from databricks.labs.lakebridge.errors.exceptions import IllegalStateException
38
- from databricks.labs.lakebridge.helpers.file_utils import is_sql_file, is_dbt_project_file
38
+ from databricks.labs.lakebridge.helpers.file_utils import chdir, is_dbt_project_file, is_sql_file
39
39
  from databricks.labs.lakebridge.transpiler.transpile_engine import TranspileEngine
40
40
  from databricks.labs.lakebridge.transpiler.transpile_status import (
41
41
  CodePosition,
@@ -389,6 +389,14 @@ class LSPEngine(TranspileEngine):
389
389
  self._client = _LanguageClient(name, version)
390
390
  self._init_response: InitializeResult | None = None
391
391
 
392
+ @property
393
+ def transpiler_name(self) -> str:
394
+ return self._config.name
395
+
396
+ def options_for_dialect(self, source_dialect: str) -> list[LSPConfigOptionV1]:
397
+ """Get the options supported when transpiling a given source dialect."""
398
+ return self._config.options_for_dialect(source_dialect)
399
+
392
400
  @property
393
401
  def supported_dialects(self) -> list[str]:
394
402
  return self._config.remorph.dialects
@@ -400,15 +408,14 @@ class LSPEngine(TranspileEngine):
400
408
  async def initialize(self, config: TranspileConfig) -> None:
401
409
  if self.is_alive:
402
410
  raise IllegalStateException("LSP engine is already initialized")
403
- cwd = os.getcwd()
404
411
  try:
405
- os.chdir(self._workdir)
406
- await self._do_initialize(config)
412
+ # TODO: Avoid this by setting the working directory when launching the child process.
413
+ with chdir(self._workdir):
414
+ await self._do_initialize(config)
407
415
  await self._await_for_transpile_capability()
408
416
  # it is good practice to catch broad exceptions raised by launching a child process
409
417
  except Exception as e: # pylint: disable=broad-exception-caught
410
418
  logger.error("LSP initialization failed", exc_info=e)
411
- os.chdir(cwd)
412
419
 
413
420
  async def _do_initialize(self, config: TranspileConfig) -> None:
414
421
  await self._start_server()
@@ -523,9 +530,7 @@ class LSPEngine(TranspileEngine):
523
530
  self.close_document(file_path)
524
531
  return ChangeManager.apply(source_code, response.changes, response.diagnostics, file_path)
525
532
 
526
- def open_document(self, file_path: Path, encoding="utf-8", source_code: str | None = None) -> None:
527
- if source_code is None:
528
- source_code = file_path.read_text(encoding)
533
+ def open_document(self, file_path: Path, source_code: str) -> None:
529
534
  text_document = TextDocumentItem(
530
535
  uri=file_path.as_uri(), language_id=LanguageKind.Sql, version=1, text=source_code
531
536
  )
@@ -39,6 +39,10 @@ class SqlglotEngine(TranspileEngine):
39
39
  def supported_dialects(self) -> list[str]:
40
40
  return sorted(SQLGLOT_DIALECTS.keys())
41
41
 
42
+ @property
43
+ def transpiler_name(self) -> str:
44
+ return "sqlglot"
45
+
42
46
  def _partial_transpile(
43
47
  self,
44
48
  read_dialect: Dialect,
@@ -37,13 +37,11 @@ class TranspileEngine(abc.ABC):
37
37
 
38
38
  @property
39
39
  @abc.abstractmethod
40
- def supported_dialects(self) -> list[str]: ...
40
+ def transpiler_name(self) -> str: ...
41
41
 
42
- def check_source_dialect(self, source_dialect: str | None) -> None:
43
- if source_dialect not in self.supported_dialects:
44
- raise ValueError(
45
- f"Invalid value for '--source-dialect': '{source_dialect}' is not one of {self.supported_dialects}."
46
- )
42
+ @property
43
+ @abc.abstractmethod
44
+ def supported_dialects(self) -> list[str]: ...
47
45
 
48
46
  @abc.abstractmethod
49
47
  def is_supported_file(self, file: Path) -> bool: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-labs-lakebridge
3
- Version: 0.10.1
3
+ Version: 0.10.3
4
4
  Summary: Fast and predictable migrations to Databricks Lakehouse Platform. This tool is designed to help you migrate your data and workloads to the Databricks Lakehouse Platform in a fast, predictable, and reliable way. It provides a set of tools and utilities to help you reconcile your data and workloads, assess your current state, and plan your migration.
5
5
  Project-URL: Documentation, https://databrickslabs.github.io/lakebridge
6
6
  Project-URL: Issues, https://github.com/databrickslabs/lakebridge/issues
@@ -25,8 +25,8 @@ Classifier: Topic :: Software Development :: Libraries
25
25
  Classifier: Topic :: Utilities
26
26
  Requires-Python: >=3.10
27
27
  Requires-Dist: cryptography<45.1.0,>=44.0.2
28
- Requires-Dist: databricks-bb-analyzer~=0.1.7
29
- Requires-Dist: databricks-labs-blueprint[yaml]<0.12.0,>=0.11.0
28
+ Requires-Dist: databricks-bb-analyzer~=0.1.9
29
+ Requires-Dist: databricks-labs-blueprint[yaml]<0.12.0,>=0.11.2
30
30
  Requires-Dist: databricks-labs-lsql==0.16.0
31
31
  Requires-Dist: databricks-sdk~=0.51.0
32
32
  Requires-Dist: duckdb~=1.2.2
@@ -1,17 +1,19 @@
1
1
  docs/lakebridge/src/components/Button.tsx,sha256=5l_irZl4AGwK7k1e2rdOb_W2-305Q1mjwXA3iP8CqaM,3159
2
+ docs/lakebridge/src/components/ReconcileTabs.tsx,sha256=xJD0nq_raoYv70YLEnG2iuAUTSXXvDpmtmjX7X9Tw9E,2665
2
3
  docs/lakebridge/src/css/custom.css,sha256=-XnDdVlHqJZXJmKarH7zCUMnnlAfpxIpZyr8FNJ4q0A,4024
3
4
  docs/lakebridge/src/css/table.css,sha256=_MAyY7hyhfFrSNVAvCA2QlqdbeBi4Kr9Ue93bSyhKSE,315
4
5
  docs/lakebridge/src/pages/index.tsx,sha256=fQRA9ZbKsPxZbXuSa1LMDk1xfYg2YXCFgsgzqus0NLc,1789
6
+ docs/lakebridge/src/theme/DocSidebarItems/index.tsx,sha256=3FHRUOXJtjQk-caU_xAmmJWPC3E-H_ZZySV23tzqz3A,1334
5
7
  docs/lakebridge/src/theme/Footer/index.tsx,sha256=Jj8zY5WDiTLXwF_mAgld8Dh1A3MY1HFVVSYIoUs51so,1057
6
8
  docs/lakebridge/src/theme/Layout/index.tsx,sha256=IkdLr13jKmLxT0jWQqrwqrjVXc8Rwd_kWNpTd1t2sc0,592
7
9
  databricks/__init__.py,sha256=YqH8Hy8lHJxd0hLMZF6kWirUDdPiX90LRDX6S6yTMn0,261
8
10
  databricks/labs/__init__.py,sha256=YqH8Hy8lHJxd0hLMZF6kWirUDdPiX90LRDX6S6yTMn0,261
9
- databricks/labs/lakebridge/__about__.py,sha256=yME1bXdQJp7l83rLxzUY__9sQJos-JhiNDfB3KbW3WA,49
11
+ databricks/labs/lakebridge/__about__.py,sha256=LBCN0OI_6vUqxgIo75HVdQc1TP5LOmy5HIs3OKrDIpk,49
10
12
  databricks/labs/lakebridge/__init__.py,sha256=nUNECqNvyfpT0aeWwlqG0ADT8U8ScCLb8WWpLydppcA,464
11
13
  databricks/labs/lakebridge/base_install.py,sha256=8NxXsNpgqXnuADKXVFh5oQL3osdvygRMY1amJwKfU08,490
12
- databricks/labs/lakebridge/cli.py,sha256=BgN1pz4dtLB_Y0C16_JhcRVxIfU7srZk24tUBSLJPAs,20597
13
- databricks/labs/lakebridge/config.py,sha256=Kxl_Yzo5ooiFrt95Gp7AwyLlPZopa4MmQKpBfpHso2Y,5872
14
- databricks/labs/lakebridge/install.py,sha256=e7MdIWFHHr2-sp-NNe6inJ44s_jgL-b_g_Y4MJXtqNQ,38724
14
+ databricks/labs/lakebridge/cli.py,sha256=6exPUJs7c2qVo-X9VXFg5VM3XqOCdlk0_5OXfPw6nbY,31578
15
+ databricks/labs/lakebridge/config.py,sha256=IjxvphM9fRQHQ2FAxwZ23deJGgSemJ3rMV0sp1Ob6e8,5833
16
+ databricks/labs/lakebridge/install.py,sha256=EmtzbC-pOeiK7lqn4wxSRoeODlkqB_lQBJ9Mj4E0kjE,40536
15
17
  databricks/labs/lakebridge/jvmproxy.py,sha256=F9pXpemzdaJXwpshHxVM9PYU_eNn4zTCUFQ5vc9WIhA,1573
16
18
  databricks/labs/lakebridge/lineage.py,sha256=Q2oky4RkODRHWMwIQIwbYXSdZTmRkMWwEh6RssBiQxY,1843
17
19
  databricks/labs/lakebridge/uninstall.py,sha256=hf36YgeW9XO2cRvvn6AXUZdihQ1ZMHnR38OVEF5sfRw,759
@@ -45,19 +47,19 @@ databricks/labs/lakebridge/errors/exceptions.py,sha256=PIj8wRJpxrBXOLMMt9HQhBfhZ
45
47
  databricks/labs/lakebridge/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
48
  databricks/labs/lakebridge/helpers/db_sql.py,sha256=chFHpn6XIuC0GrJ3a30_Y7tcXd4KZ5qO9zCAI4d7TR0,806
47
49
  databricks/labs/lakebridge/helpers/execution_time.py,sha256=8oLEYh0AKz1fuiQMyDTWDymhxh6xUKlcFpINWzKnOy4,533
48
- databricks/labs/lakebridge/helpers/file_utils.py,sha256=rzZ0IBu7dlPNRtzLnzOv4sZZd2FBPPQNdodbkb3PQEI,1991
50
+ databricks/labs/lakebridge/helpers/file_utils.py,sha256=1X3ri7_kyZibOFq36mX8fiERhE3tru_7VZIat1jjzOc,1911
49
51
  databricks/labs/lakebridge/helpers/metastore.py,sha256=1SKsIfNtiu3jUFjaXZ5B1fBZigVYqS1Q2OWhdn9qa8U,6425
50
52
  databricks/labs/lakebridge/helpers/recon_config_utils.py,sha256=1Nq_pIonE2tz08kdVpSDS-NVKGZ1p_kGRZBUQFFWZAs,7404
51
- databricks/labs/lakebridge/helpers/string_utils.py,sha256=_ovbMl7lXMKgYi1AF5UZQBcF1ZDs1PsSJV05erbEvg4,2786
53
+ databricks/labs/lakebridge/helpers/string_utils.py,sha256=TKW0BHmOZ2G8EebCohQRJLYglqeJajHgQ2BLehf9qsE,1169
52
54
  databricks/labs/lakebridge/helpers/telemetry_utils.py,sha256=M0lqYcLdLKROnnu3KRJUlGS368IRPIACef4G1ae9cvA,435
53
55
  databricks/labs/lakebridge/helpers/validation.py,sha256=97AoCcsliWKUBKAY8SwgL4Dad-r_W59L_7h2I3Pudmk,4449
54
56
  databricks/labs/lakebridge/intermediate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
57
  databricks/labs/lakebridge/intermediate/dag.py,sha256=47bgyaYaBK_ELwLE5VGgFUraSxKdMJkLmo2lfc602lI,3165
56
58
  databricks/labs/lakebridge/intermediate/engine_adapter.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
- databricks/labs/lakebridge/intermediate/root_tables.py,sha256=_3I0ks9qi9Al6HcURZ-gZ35IuP6dk7RUVuAOZHMcHp0,1497
59
+ databricks/labs/lakebridge/intermediate/root_tables.py,sha256=G9PFU22qJ0BgV1FGZPK5bWNdEa8Xpo_gyEvMmATHkTw,1524
58
60
  databricks/labs/lakebridge/reconcile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
61
  databricks/labs/lakebridge/reconcile/compare.py,sha256=P9ABIT95TeS7BVRYVbzjpaEsynF2h2m5M8f9-he8A3A,16136
60
- databricks/labs/lakebridge/reconcile/constants.py,sha256=TrWuQFgSoBhRZkpiuscewG5eaZKV-FsErUQt4JO0cxo,798
62
+ databricks/labs/lakebridge/reconcile/constants.py,sha256=ZXhGp0hxNdCWTN0iOfaIiDvRkxMZm4E7vtL-tVDsImM,816
61
63
  databricks/labs/lakebridge/reconcile/exception.py,sha256=kA-1KVAgZfWzxhcUwYha_8OapmFajJG0iY5TxPUPJyQ,1463
62
64
  databricks/labs/lakebridge/reconcile/execute.py,sha256=13yDonKuOcGytIDEySgAF--8VC_zLR4-hLudD2EkE0g,35111
63
65
  databricks/labs/lakebridge/reconcile/recon_capture.py,sha256=mlrKSzeTQnq3_ncbTunE1OyIFA2bLKlwiuDMicQRf5c,27317
@@ -73,8 +75,8 @@ databricks/labs/lakebridge/reconcile/connectors/jdbc_reader.py,sha256=SsY1rkeLo4
73
75
  databricks/labs/lakebridge/reconcile/connectors/oracle.py,sha256=LBqlK5WbgB4XaQNJ_DomTHXazdHJNu4vkIic_z6UENw,4795
74
76
  databricks/labs/lakebridge/reconcile/connectors/secrets.py,sha256=vue72BaYVaaeUfTOaqIEwP-I3TApgbPiuq69Z6I2u3k,1125
75
77
  databricks/labs/lakebridge/reconcile/connectors/snowflake.py,sha256=ARooTfPo6Vvrrj1n3KQ6aW-raAkoY_Z_qHB6epa5WVI,8086
76
- databricks/labs/lakebridge/reconcile/connectors/source_adapter.py,sha256=N_qbIuJsU7q3M3WWLv36DTKH-zYqV7js7Vcdx9Z7fLo,1449
77
- databricks/labs/lakebridge/reconcile/connectors/sql_server.py,sha256=vvjNyw8ZhO-IqMMCuSVcD19vPOrWTh2WH5ndoxKybHE,5675
78
+ databricks/labs/lakebridge/reconcile/connectors/source_adapter.py,sha256=I6LBE0C8e80lMm_lVBVIrW9g9ogIgZ53J_EFRNkcSWY,1445
79
+ databricks/labs/lakebridge/reconcile/connectors/tsql.py,sha256=71ChvUvDWSp6qftl4cJ7B_ztnchpU7uXo1_zLl5cDbc,5676
78
80
  databricks/labs/lakebridge/reconcile/query_builder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
81
  databricks/labs/lakebridge/reconcile/query_builder/aggregate_query.py,sha256=zCPmLBLWeKwn0E2QMs0ua2CIJ6cnxmn77mNt4lvauTw,13783
80
82
  databricks/labs/lakebridge/reconcile/query_builder/base.py,sha256=J1LSemcN6bn-0K5U1PhXaQj22axOmqHUv-s9WwLQZOk,5293
@@ -145,16 +147,16 @@ databricks/labs/lakebridge/resources/reconcile/queries/installation/details.sql,
145
147
  databricks/labs/lakebridge/resources/reconcile/queries/installation/main.sql,sha256=s_A0YyGSX_pCWnQsQnY65VYFcbNvq2qKJvYxU6zam6E,794
146
148
  databricks/labs/lakebridge/resources/reconcile/queries/installation/metrics.sql,sha256=FdvjQp7gCwsbcu4UrOuJN-bBLJFpvUIyxH6PQvg04Wo,1006
147
149
  databricks/labs/lakebridge/transpiler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
148
- databricks/labs/lakebridge/transpiler/execute.py,sha256=EOVAXHFzmfsS7cA5ogWKEUxGG6KokR-7PDZ8BdVwuz8,16871
149
- databricks/labs/lakebridge/transpiler/transpile_engine.py,sha256=9o-MXAnCChbFxv9Kg8kGLXdc8BZmtlwV5JdMPiuTQNk,1827
150
+ databricks/labs/lakebridge/transpiler/execute.py,sha256=7DpeIixATOPryyt4TD93-sdwE1C_fIwuo6bKwClaF_s,17007
151
+ databricks/labs/lakebridge/transpiler/transpile_engine.py,sha256=5zC8fkpBBlt9RjE_BeA_Sd6vaRxA3mBdhTqoRGFTc_Y,1616
150
152
  databricks/labs/lakebridge/transpiler/transpile_status.py,sha256=MO-Ju-ki3FCY15WxgwfPV9EC7Ma9q8aIfSTgHAmnkGU,1715
151
153
  databricks/labs/lakebridge/transpiler/lsp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
152
- databricks/labs/lakebridge/transpiler/lsp/lsp_engine.py,sha256=6H12u41I6HxXqF8SII6janxXkT4GG9VcrsDFeOmA7Cg,22325
154
+ databricks/labs/lakebridge/transpiler/lsp/lsp_engine.py,sha256=osT4RXpYqBNcAQ8mcoFt8m2dygs5TcmYnQq57KN_kw4,22580
153
155
  databricks/labs/lakebridge/transpiler/sqlglot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
154
156
  databricks/labs/lakebridge/transpiler/sqlglot/dialect_utils.py,sha256=GhXXWGA_2PlmHKjxrjryZpA5xaVZ81Vrw3b7DzjpFFI,1033
155
157
  databricks/labs/lakebridge/transpiler/sqlglot/lca_utils.py,sha256=vpDLGhE-wFMah1VTXkMg6gI_QnzdzpYZf0h9DUd8zcI,5154
156
158
  databricks/labs/lakebridge/transpiler/sqlglot/local_expression.py,sha256=V69eEJHyZKxmyaham6OulYnwQRqkbGUrdiWm1EWP8YE,3825
157
- databricks/labs/lakebridge/transpiler/sqlglot/sqlglot_engine.py,sha256=3-YTEDPCk3Q7nT6A09D0p_IH8ftC3wr4sPF23Yy_OJM,10053
159
+ databricks/labs/lakebridge/transpiler/sqlglot/sqlglot_engine.py,sha256=1uqpYIB-6vhuFqco80lXyBqqdkVkZkk9xuqFAvf2kXI,10131
158
160
  databricks/labs/lakebridge/transpiler/sqlglot/generator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
159
161
  databricks/labs/lakebridge/transpiler/sqlglot/generator/databricks.py,sha256=tF38z3J-P0mDnGeDmCzAiowAUoShiosimM6nfR_-3Ro,30653
160
162
  databricks/labs/lakebridge/transpiler/sqlglot/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -163,9 +165,9 @@ databricks/labs/lakebridge/transpiler/sqlglot/parsers/presto.py,sha256=bY6Ku8ZPW
163
165
  databricks/labs/lakebridge/transpiler/sqlglot/parsers/snowflake.py,sha256=dZ7BdOlBZlkbiN9G9bu4l2c456265Gx9WoWUPRa7Ffg,23203
164
166
  databricks/labs/lakebridge/upgrades/v0.4.0_add_main_table_operation_name_column.py,sha256=wMTbj1q5td4fa5DCk0tWFJ-OmhhzsExRLYUe4PKmk0s,3527
165
167
  databricks/labs/lakebridge/upgrades/v0.6.0_alter_metrics_datatype.py,sha256=hnTHRtqzwPSF5Judzh6ss-uB5h3IFtm2ylWduwRNq5Y,2424
166
- databricks_labs_lakebridge-0.10.1.dist-info/METADATA,sha256=nGDYIxZ1D8NBXRz5ePlIUsulJHW-ljJ9QHdxhVZrqck,3078
167
- databricks_labs_lakebridge-0.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
168
- databricks_labs_lakebridge-0.10.1.dist-info/entry_points.txt,sha256=Idr1CT73b8wShdr287yu1hheGbDbhBvucVUlZcbpiPo,75
169
- databricks_labs_lakebridge-0.10.1.dist-info/licenses/LICENSE,sha256=1hG0Cvw6mp9nL9qRoHFcCUk9fYqhcnj2vgJ75rt3BxA,3862
170
- databricks_labs_lakebridge-0.10.1.dist-info/licenses/NOTICE,sha256=wtxMsNvTkw1hAEkkWHz8A8JrYySAUSt1tOTcqddkWEg,1797
171
- databricks_labs_lakebridge-0.10.1.dist-info/RECORD,,
168
+ databricks_labs_lakebridge-0.10.3.dist-info/METADATA,sha256=e7yr--8po1oLKE8_BRRTFbv_y2fJ0Fw5F95wT2bnn8U,3078
169
+ databricks_labs_lakebridge-0.10.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
170
+ databricks_labs_lakebridge-0.10.3.dist-info/entry_points.txt,sha256=Idr1CT73b8wShdr287yu1hheGbDbhBvucVUlZcbpiPo,75
171
+ databricks_labs_lakebridge-0.10.3.dist-info/licenses/LICENSE,sha256=1hG0Cvw6mp9nL9qRoHFcCUk9fYqhcnj2vgJ75rt3BxA,3862
172
+ databricks_labs_lakebridge-0.10.3.dist-info/licenses/NOTICE,sha256=wtxMsNvTkw1hAEkkWHz8A8JrYySAUSt1tOTcqddkWEg,1797
173
+ databricks_labs_lakebridge-0.10.3.dist-info/RECORD,,
@@ -0,0 +1,86 @@
1
+ import React, {useRef,useEffect} from 'react';
2
+ import Tabs from '@theme/Tabs';
3
+ import TabItem from '@theme/TabItem';
4
+ import useBaseUrl from '@docusaurus/useBaseUrl';
5
+
6
+ type FrameTabProps = {
7
+ src: string;
8
+ label: string;
9
+ };
10
+
11
+ const frameStyle: React.CSSProperties = {
12
+ width: '100%',
13
+ height: '500px',
14
+ border: '1px solid #ccc',
15
+ borderRadius: '6px',
16
+ marginBottom: '1em',
17
+ overflow: 'auto',
18
+ };
19
+
20
+ const FrameTab: React.FC<FrameTabProps> = ({ src, label }) => {
21
+ const iframeRef = useRef<HTMLIFrameElement>(null);
22
+ const url = useBaseUrl(src);
23
+
24
+ useEffect(() => {
25
+ const iframe = iframeRef.current;
26
+ if (!iframe) return;
27
+
28
+ const onLoad = () => {
29
+ try {
30
+ const doc = iframe.contentDocument || iframe.contentWindow?.document;
31
+ if (!doc) return;
32
+
33
+ const elementsToRemove = doc.querySelectorAll(
34
+ 'div[data-testid="content-spacer"], div[data-testid="extra-whitespace"]'
35
+ );
36
+
37
+ elementsToRemove.forEach(el => {
38
+ if (!el.children.length) {
39
+ el.remove();
40
+ }
41
+ });
42
+ } catch (err) {
43
+ console.warn(err);
44
+ }
45
+ };
46
+
47
+ iframe.addEventListener('load', onLoad);
48
+
49
+ return () => {
50
+ iframe.removeEventListener('load', onLoad);
51
+ };
52
+ }, [url]);
53
+
54
+ return (
55
+ <div>
56
+ <div style={{ marginBottom: '0.5em' }}>
57
+ <a href={url} target="_blank" rel="noopener noreferrer">
58
+ Open notebook in new tab
59
+ </a>
60
+ </div>
61
+ <iframe ref={iframeRef} src={url} style={frameStyle} title={label} />
62
+ </div>
63
+ );
64
+ };
65
+
66
+ const LakebridgeTabs: React.FC = () => (
67
+ <Tabs>
68
+ {/* <TabItem value="Readme" label="Readme" default>
69
+ <FrameTab src="/lakebridge_reconcile/Readme.html" label="Readme" />
70
+ </TabItem>*/}
71
+ <TabItem value="Recon Main" label="Recon Main">
72
+ <FrameTab src="/lakebridge_reconcile/lakebridge_recon_main.html" label="Recon Main" />
73
+ </TabItem>
74
+ <TabItem value="Recon Wrapper" label="Recon Wrapper">
75
+ <FrameTab src="/lakebridge_reconcile/recon_wrapper_nb.html" label="Recon Wrapper" />
76
+ </TabItem>
77
+ <TabItem value="Snowflake Example" label="Transformation Query Generator">
78
+ <FrameTab
79
+ src="/lakebridge_reconcile/snowflake_transformation_query_generator.html"
80
+ label="Query Generator"
81
+ />
82
+ </TabItem>
83
+ </Tabs>
84
+ );
85
+
86
+ export default LakebridgeTabs;