spark-connect 0.3.0 → 0.4.0

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +144 -27
  3. data/README.md +29 -11
  4. data/lib/spark_connect/catalog.rb +157 -12
  5. data/lib/spark_connect/channel_builder.rb +119 -12
  6. data/lib/spark_connect/client.rb +233 -17
  7. data/lib/spark_connect/column.rb +143 -10
  8. data/lib/spark_connect/conf.rb +16 -0
  9. data/lib/spark_connect/data_frame.rb +428 -23
  10. data/lib/spark_connect/functions.rb +1947 -493
  11. data/lib/spark_connect/grouped_data.rb +33 -7
  12. data/lib/spark_connect/merge.rb +146 -0
  13. data/lib/spark_connect/na_functions.rb +43 -9
  14. data/lib/spark_connect/observation.rb +26 -5
  15. data/lib/spark_connect/pipelines.rb +101 -0
  16. data/lib/spark_connect/plan.rb +52 -0
  17. data/lib/spark_connect/proto/spark/connect/base_pb.rb +6 -1
  18. data/lib/spark_connect/proto/spark/connect/base_services_pb.rb +2 -0
  19. data/lib/spark_connect/proto/spark/connect/catalog_pb.rb +11 -1
  20. data/lib/spark_connect/proto/spark/connect/commands_pb.rb +1 -1
  21. data/lib/spark_connect/proto/spark/connect/expressions_pb.rb +3 -1
  22. data/lib/spark_connect/proto/spark/connect/pipelines_pb.rb +5 -1
  23. data/lib/spark_connect/proto/spark/connect/relations_pb.rb +4 -1
  24. data/lib/spark_connect/proto/spark/connect/types_pb.rb +3 -1
  25. data/lib/spark_connect/reader.rb +98 -17
  26. data/lib/spark_connect/session.rb +232 -30
  27. data/lib/spark_connect/stat_functions.rb +19 -9
  28. data/lib/spark_connect/storage_level.rb +79 -0
  29. data/lib/spark_connect/streaming.rb +335 -20
  30. data/lib/spark_connect/table_arg.rb +73 -0
  31. data/lib/spark_connect/tvf.rb +78 -0
  32. data/lib/spark_connect/types.rb +149 -0
  33. data/lib/spark_connect/version.rb +2 -2
  34. data/lib/spark_connect/writer.rb +80 -10
  35. data/lib/spark_connect.rb +4 -0
  36. data/proto/spark/connect/base.proto +93 -1
  37. data/proto/spark/connect/catalog.proto +81 -0
  38. data/proto/spark/connect/commands.proto +7 -0
  39. data/proto/spark/connect/expressions.proto +28 -0
  40. data/proto/spark/connect/pipelines.proto +92 -5
  41. data/proto/spark/connect/relations.proto +71 -1
  42. data/proto/spark/connect/types.proto +19 -0
  43. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27eea75da0f1e6659b7371732fc56d2f103e0e79f60662760046303b0af5671f
4
- data.tar.gz: 20676c87b08bd17e425722cc74b8fdbd2a2e4fb97e4c0bb15b63a9f2a1602e45
3
+ metadata.gz: 0e74ad29eb515f38a9735a8d719adc6fb38d8d7cbb7719ca5cf69b43ae4b32de
4
+ data.tar.gz: f9006bc6e7f05631175457fe4d8219f023456d544ff9d1d377ac918dcc4ad9af
5
5
  SHA512:
6
- metadata.gz: 3ca57c3c06de5909d836c9f3da9fd52eb70016262e8cbf5be90bf0082ff579c63c49b7586592eb6e465a090f16126a2e47fdac4f47c21402e4af2da356b2dfea
7
- data.tar.gz: e68ec46d9a26c71201a456191847825c85d842bcfb76d2001eab07f6c5bbf7147f43feea68eeb85472f2d9b5a91a9ea5dcd43a80a70225fa48edf4e1a35deace
6
+ metadata.gz: 975ca916148a4005eddfe9db971746f0128b5190251dde7dcc8e287cae267df8169d3446136c195fc4e2298945e5ad1a6a1c70754c2ca12ae95b00b29eb3ceb2
7
+ data.tar.gz: 9df3247554b07123aedfc0cbc5c790f5cda158fe328ff3d8145f311ab9ac36acf35ad3d0489939b6ea68853fdaf736a66b791652f42ef612222f27596f6b556c
data/CHANGELOG.md CHANGED
@@ -7,35 +7,152 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [0.3.0] - 2026-06-15
10
+ ## [0.4.0] - 2026-11-04
11
+
12
+ Feature parity with the PySpark 4.3.0 Spark Connect client. Items marked with a
13
+ Spark version need a server of at least that version.
14
+
15
+ ### Added
11
16
 
12
- Minor-version release. Contains the same fixes as 0.2.1 (listed below); there
13
- are no functional changes beyond 0.2.1.
17
+ - **Spark Connect 4.3.0 protocol.** Regenerated the vendored protobuf/gRPC stubs
18
+ from Spark v4.3.0 (`SparkConnect::SPARK_VERSION` is now `"4.3.0"`).
19
+ - **Every `pyspark.sql.functions` function** except the UDF family: 280 new
20
+ functions (559 of PySpark's 562), including the HLL/KLL/Theta/Tuple sketch,
21
+ bitmap, variant (with the 4.3 `variant_set`/`variant_insert`/`variant_delete`/
22
+ `variant_array_append` family), vector (4.3), geospatial (`st_*`), TIME
23
+ (`make_time`, `time_*`, `current_time`), nanosecond timestamp (4.3), XML/CSV,
24
+ AES, URL, collation, `try_*`, interval, partition transform, `listagg`/
25
+ `string_agg`, `count_min_sketch`, `hmac`, base32, `jaro_winkler_similarity`,
26
+ `call_function`, `assert_true`/`raise_error`, and `broadcast` functions, plus
27
+ the PySpark 4.3 parameters `instr(..., start, occurrence)`,
28
+ `regexp_replace(..., position)`, and `max_by`/`min_by(..., k)`, an
29
+ `array_sort` comparator, and PySpark's camelCase aliases (`bitwiseNOT`,
30
+ `shiftLeft`, `toDegrees`, ...). `udf`, `udtf`, and `arrow_udtf` raise
31
+ `NotImplementedError`.
32
+ - **DataFrame**: `zip` (4.3), `nearest_by_join` (4.2), `repartition_by_id` (4.1),
33
+ `lateral_join`, `transpose`, `scalar`/`exists` subqueries, `as_table` table
34
+ arguments (`TableArg`), `group_by(..., grouping_sets:)`, `merge_into`
35
+ (`MergeIntoWriter`), `zip_with_index`, `tail`, `with_metadata`,
36
+ `metadata_column`, `random_split`, `cache`/`persist`/`unpersist`/
37
+ `storage_level`/`cached?` with `SparkConnect::StorageLevel`,
38
+ `local_checkpoint(storage_level:)`, `register_temp_table`, `df[...]` by name,
39
+ index, Column (filter), or Array (select), the NA/stat shortcuts (`dropna`,
40
+ `fillna`, `replace`, `approx_quantile`, `corr`, `cov`, `crosstab`,
41
+ `freq_items`, `sample_by`), and PySpark camelCase aliases (`isEmpty`,
42
+ `sameSemantics`, `semanticHash`, `inputFiles`, ...).
43
+ - **Column**: `isin(df)` IN-subqueries (Spark 4.1+), `try_cast`,
44
+ `with_field`, `drop_fields`, `outer`, `transform`, qualified stars
45
+ (`F.col("t.*")`), and the `bitwiseAND`/`bitwiseOR`/`bitwiseXOR`/
46
+ `eqNullSafe`/`isNaN`/`getItem`/`getField` aliases.
47
+ - **Session**: `empty_data_frame`, `clone_session` (4.1), `tvf`
48
+ (table-valued functions), progress handlers
49
+ (`register_progress_handler`/`remove_progress_handler`/
50
+ `clear_progress_handlers`, reporting `StageInfo`s), `stopped?`, and the
51
+ `SparkSession.active`/`get_active_session`/`default_session` lookups. The
52
+ builder gained `channel_builder`, `config(hash)`, a block form that stops the
53
+ session afterwards, `master` (no-op), and `enable_hive_support` (raises, as
54
+ in PySpark).
55
+ - **Client**: `get_operation_statuses` (the 4.2 `GetStatus` RPC), per-RPC
56
+ deadlines (`SparkConnect::RpcDeadlines`), and `close`/`closed?`.
57
+ - **Connection strings**: the `grpc_keepalive_enabled`,
58
+ `grpc_keepalive_time_ms`, `grpc_keepalive_timeout_ms`, and
59
+ `grpc_keepalive_without_calls` parameters (keepalive is on by default, like
60
+ the 4.3 clients), a `SPARK_CONNECT_AUTHENTICATE_TOKEN` default for `token`,
61
+ and 128 MiB gRPC message limits.
62
+ - **Catalog** (4.2): `drop_table`, `drop_view`, `create_database`,
63
+ `drop_database`, `list_partitions`, `list_views`, `get_table_properties`,
64
+ `get_create_table_string`, `truncate_table`, and `analyze_table`; plus
65
+ `get_database`, `get_table`, `get_function`, `refresh_by_path`, name
66
+ patterns for the `list_*` methods, `cache_table(storage_level)`, and
67
+ camelCase aliases.
68
+ - **Reading and writing**: `read.changes` (4.2), `read.xml`, parsing a
69
+ DataFrame of JSON/CSV/XML strings (`read.json(df)`; XML needs 4.2),
70
+ partitioned `read.jdbc`; `write.cluster_by`, `write.xml`, `write.jdbc`,
71
+ `with_schema_evolution` (4.2) on both writers, and `write_to(...).options`/
72
+ `partitioned_by`/`cluster_by`.
73
+ - **Structured Streaming**: `read_stream.name` (named sources, 4.2),
74
+ `read_stream.changes` (4.2), `read_stream.xml`, `write_stream.cluster_by`,
75
+ `trigger(real_time: ...)` (4.2), keyword shortcuts on `start`/`to_table`,
76
+ `StreamingQueryListener` with `streams.add_listener`/`remove_listener`, and
77
+ camelCase aliases on `StreamingQuery`.
78
+ - **Declarative Pipelines**: `create_streaming_table` and
79
+ `create_auto_cdc_flow` (4.2).
80
+ - **Types**: `TimeType` and `GeometryType`/`GeographyType` (4.1),
81
+ `TimestampNTZNanosType`/`TimestampLTZNanosType` (4.3), and decoding of
82
+ user-defined types (`UserDefinedType`); TIME values decode to `Arrow::Time`
83
+ and nanosecond timestamps keep their nanoseconds.
84
+ - `RuntimeConfig#set_all` and the `is_modifiable`/`isModifiable` aliases.
85
+ - `Observation#get` raises the server's error when a metric failed (4.2).
86
+ - Spark 4.3.0 is now the first entry of the CI integration matrix.
87
+
88
+ ### Changed
89
+
90
+ - **Function arguments follow PySpark's conventions exactly** (each function
91
+ was checked against PySpark 4.3.0 expression for expression). Where PySpark
92
+ takes a column, a String is a column name, so `F.date_add("d", "n")`,
93
+ `F.add_months`, `F.date_sub`, `F.substring`, `F.slice`, `F.overlay`,
94
+ `F.repeat`, `F.array_repeat`, `F.array_insert`, the `split` limit, and the
95
+ `regexp_like`/`regexp_count`/`regexp_substr`/`regexp_extract_all` pattern
96
+ now read columns where they used to send String literals; wrap literals in
97
+ `F.lit`.
98
+ - **Client-side random seeds.** `F.rand`, `F.randn`, `F.uuid`, `F.shuffle` (and
99
+ the new `randstr`, `uniform`, `count_min_sketch`), `DataFrame#sample`,
100
+ `random_split`, and `sample_by` pick a seed on the client when none is given,
101
+ so a DataFrame returns the same values on every action. On Spark 3.5
102
+ servers, `F.uuid`, `F.shuffle`, and `F.mode` (which now sends its
103
+ `deterministic` flag) need `F.expr` instead.
104
+ - `F.mean`, `F.pow`, and one-argument `F.log` send `avg`, `power`, and `ln`
105
+ like PySpark, so their result columns are named accordingly; the base of
106
+ `F.log(base, col)` is a literal.
107
+ - `F.lit(BigDecimal(...))` is tagged `decimal(10, 0)` like PySpark; the server
108
+ widens it to fit the value (`1.5` becomes `decimal(10,1)` instead of
109
+ `decimal(2,1)`).
110
+ - `df["x"]`, `df.col("x")`, `df.x`, `col_regex`, and `pivot("x")` refer to that
111
+ DataFrame's column (bound to its plan id, like PySpark), which disambiguates
112
+ self-joins. `group_by`/`rollup`/`cube` accept 1-based ordinals, and `join`
113
+ accepts an Array of Column conditions (combined with AND) and rejects
114
+ DataFrames from another session.
115
+ - `GroupedData`: `sum`/`avg`/`mean`/`max`/`min` without columns aggregate
116
+ every numeric column; `agg` without expressions raises; `pivot` is only
117
+ allowed directly after `group_by` (`NotImplementedError` otherwise).
118
+ - `DataFrameNaFunctions`: `drop`, `fill`, and `replace` validate their
119
+ arguments like PySpark; `replace` needs a `value` unless `to_replace` is a
120
+ Hash, and integer replacement values are sent as doubles.
121
+ - `StatFunctions#corr` raises unless the method is `"pearson"`.
122
+ - Reader/writer/stream `option(key, nil)` is ignored (it used to send `""`),
123
+ and `DataFrameWriter#mode(nil)` is a no-op.
124
+ - Builder options are applied with one request that silently skips options the
125
+ session cannot change (as PySpark does), and `spark.remote`/`spark.master`
126
+ are never sent; `get_or_create` applies them to an existing session too, and
127
+ `create` makes the new session active.
128
+ - `new_session` follows PySpark 4.3: always a fresh, empty server-side session
129
+ against the same endpoint.
130
+ - `SparkSession#stop` also closes the gRPC channel and stops the listener bus,
131
+ and is idempotent.
132
+ - Every connection-string parameter the client does not consume is now sent as
133
+ gRPC request metadata (it used to be only `x-*` ones), and `session_id` must
134
+ be a UUID (`ConnectionError` otherwise).
135
+ - Non-streaming RPCs have deadlines (1 hour for `AnalyzePlan`, 10 minutes for
136
+ the others); `ExecutePlan` has none.
14
137
 
15
138
  ### Fixed
16
139
 
17
- - **Correct `TimestampType` instants in `create_data_frame`.** Timestamp columns
18
- were shipped as zone-less Arrow timestamps, so the server interpreted the epoch
19
- micros as session-local wall-clock and shifted the value by the session time
20
- zone. They are now tagged UTC; `TimestampNTZType` remains zone-less.
21
- - **`RuntimeConfig#get` with a non-String default no longer raises.** A non-String
22
- default (e.g. `conf.get(key, 8)`) was passed straight into a protobuf string
23
- field, raising `Google::Protobuf::TypeError`. The default is now coerced to a
24
- String, matching `#set`.
25
- - **No duplicate rows when an execute stream is retried.** The result accumulator
26
- was created outside the retry loop, so a mid-stream gRPC failure replayed
27
- already-consumed Arrow batches and duplicated rows on retry. The accumulator is
28
- now reset per attempt.
29
- - **`DataFrame#drop_duplicates_within_watermark` is now watermark-aware.** It was a
30
- plain alias of `#drop_duplicates` and never set the `within_watermark` flag, so
31
- it silently performed an ordinary deduplication. Added a `dropDuplicatesWithinWatermark`
32
- alias.
33
- - **`SparkSession::Builder#app_name` is now applied.** `create` explicitly skipped
34
- `spark.app.name`, making `#app_name` a no-op; all builder options are now
35
- forwarded to the new session.
36
- - Corrected misleading doc comments for `Functions#nanvl` and `DataFrame#except_all`.
140
+ - `F.bitwise_not`/`F.bitwiseNOT` and `F.negate` sent function names the server
141
+ does not resolve (`bitwise_not`, `negate`); they now send `~` and `negative`.
142
+ - `F.from_json`, `F.to_json`, and `F.schema_of_json` flattened their options
143
+ into extra arguments instead of passing one map, so any options failed.
144
+ - `F.trim`, `F.ltrim`, and `F.rtrim` with a trim string sent their arguments in
145
+ the wrong order.
146
+ - `StreamingQuery#exception` raised `NoMethodError` for a query without an
147
+ exception.
148
+ - `DataFrameReader#schema(StructType)` lost non-nullable fields and metadata;
149
+ the schema is now sent as JSON, like PySpark.
150
+ - `Observation#get` returned raw protobuf messages (decimal, array, map, and
151
+ struct metrics) or epoch integers (date and timestamp metrics).
152
+ - `new_session` reused the `session_id` of the connection string, so the "new"
153
+ session was the same server-side session.
37
154
 
38
- ## [0.2.1] - 2026-06-15
155
+ ## [0.3.0] - 2026-06-15
39
156
 
40
157
  ### Fixed
41
158
 
@@ -130,8 +247,8 @@ Initial release.
130
247
  - Vendored Spark Connect 4.1 protobuf/gRPC definitions and a regeneration script
131
248
  (`bin/generate-protos`).
132
249
 
133
- [Unreleased]: https://github.com/HyukjinKwon/spark-connect-ruby/compare/v0.3.0...HEAD
134
- [0.3.0]: https://github.com/HyukjinKwon/spark-connect-ruby/compare/v0.2.1...v0.3.0
135
- [0.2.1]: https://github.com/HyukjinKwon/spark-connect-ruby/compare/v0.2.0...v0.2.1
250
+ [Unreleased]: https://github.com/HyukjinKwon/spark-connect-ruby/compare/v0.4.0...HEAD
251
+ [0.4.0]: https://github.com/HyukjinKwon/spark-connect-ruby/compare/v0.3.0...v0.4.0
252
+ [0.3.0]: https://github.com/HyukjinKwon/spark-connect-ruby/compare/v0.2.0...v0.3.0
136
253
  [0.2.0]: https://github.com/HyukjinKwon/spark-connect-ruby/compare/v0.1.0...v0.2.0
137
254
  [0.1.0]: https://github.com/HyukjinKwon/spark-connect-ruby/releases/tag/v0.1.0
data/README.md CHANGED
@@ -40,11 +40,29 @@ spark.stop
40
40
 
41
41
  ## What it supports
42
42
 
43
- `spark-connect` implements the Spark Connect **DataFrame**, **SQL**,
44
- **Structured Streaming**, and **Declarative Pipelines** API -- everything
45
- **except** user-defined functions (UDFs) and the `foreach`/`foreachBatch`
46
- streaming sinks, whose Spark Connect protobuf definitions are not yet finalized.
47
- (The separate, experimental MLlib-over-Connect surface is also out of scope.)
43
+ `spark-connect` implements the Spark Connect **DataFrame**, **Column** and
44
+ **functions**, **SQL**, **Catalog**, **reader/writer**, **Structured
45
+ Streaming** (including query listeners), and **Declarative Pipelines** API of
46
+ PySpark 4.3: subqueries, lateral and nearest-by joins, `zip`, `merge_into`,
47
+ table-valued functions, `as_table` table arguments, caching with storage
48
+ levels, session cloning, progress handlers, operation status, the TIME,
49
+ spatial, and nanosecond timestamp types, and every built-in SQL function in
50
+ `pyspark.sql.functions`.
51
+
52
+ **Out of scope** are the parts of PySpark that run client-side code on the
53
+ server or depend on Python libraries, because Spark executes such code only in
54
+ Python (or JVM) workers and has no Ruby worker:
55
+
56
+ - user-defined functions and table functions (`udf`, `udtf`, `arrow_udtf`,
57
+ pandas and Arrow UDFs; these raise `NotImplementedError`);
58
+ - the `foreach`/`foreachBatch` streaming sinks (raise `NotImplementedError`);
59
+ - `applyInPandas`/`applyInArrow`, `transformWithState(InPandas)`, cogrouped
60
+ maps, `mapInPandas`/`mapInArrow`, and Python data sources;
61
+ - pandas interop (`toPandas`, `pandas_api`, `plot`) -- use `to_arrow` for a
62
+ columnar `Arrow::Table`;
63
+ - the UDF profiler (`spark.profile`) and artifact upload (`addArtifacts`,
64
+ `copyFromLocalToFs`), which exist to serve UDF code;
65
+ - the separate, experimental MLlib-over-Connect surface.
48
66
 
49
67
  Results decode through Apache Arrow into ordered, name-addressable `Row`s.
50
68
  Method names are snake_case (idiomatic Ruby) with camelCase aliases for the
@@ -57,7 +75,7 @@ common PySpark names (`groupBy`, `withColumn`, `orderBy`, `createDataFrame`,
57
75
  - **Apache Arrow C++/GLib system libraries** (required by the `red-arrow` dependency):
58
76
  - macOS: `brew install apache-arrow apache-arrow-glib`
59
77
  - Ubuntu/Debian: install `libarrow-glib-dev` from the [Apache Arrow APT repository](https://arrow.apache.org/install/)
60
- - A reachable **Spark Connect server**. This client is generated against the Spark Connect 4.1 protocol and supports **Apache Spark 3.5 and above**.
78
+ - A reachable **Spark Connect server**. This client is generated against the Spark Connect 4.3 protocol and supports **Apache Spark 3.5 and above**.
61
79
 
62
80
  See the [installation guide](https://hyukjinkwon.github.io/spark-connect-ruby/installation.html) for details.
63
81
 
@@ -78,9 +96,9 @@ gem "spark-connect"
78
96
  ## Running a local Spark Connect server
79
97
 
80
98
  ```bash
81
- # Download a Spark distribution (4.1.0 shown here; 3.5+ also works)
82
- curl -fsSL https://archive.apache.org/dist/spark/spark-4.1.0/spark-4.1.0-bin-hadoop3.tgz | tar xz
83
- cd spark-4.1.0-bin-hadoop3
99
+ # Download a Spark distribution (4.3.0 shown here; 3.5+ also works)
100
+ curl -fsSL https://archive.apache.org/dist/spark/spark-4.3.0/spark-4.3.0-bin-hadoop3.tgz | tar xz
101
+ cd spark-4.3.0-bin-hadoop3
84
102
 
85
103
  # Start the Connect server (requires Java 17+).
86
104
  # Spark 4.0.0+ bundles the Connect server, so no extra packages are needed.
@@ -106,7 +124,7 @@ SparkConnect::SparkSession.builder
106
124
  .get_or_create
107
125
  ```
108
126
 
109
- Supported parameters: `token`, `user_id`, `user_agent`, `use_ssl`, `session_id`, and any `x-*` custom gRPC headers.
127
+ Supported parameters: `token` (defaults to `$SPARK_CONNECT_AUTHENTICATE_TOKEN`), `user_id`, `user_agent`, `use_ssl`, `session_id`, and the gRPC keepalive settings `grpc_keepalive_enabled`, `grpc_keepalive_time_ms`, `grpc_keepalive_timeout_ms`, `grpc_keepalive_without_calls`. Any other parameter (e.g. `x-api-key=...`) is sent as a gRPC request header.
110
128
 
111
129
  ## A quick tour
112
130
 
@@ -149,7 +167,7 @@ Runnable [`examples/`](examples/) cover quickstart, transformations, aggregation
149
167
 
150
168
  ## Compatibility
151
169
 
152
- The client is generated against the **Spark Connect 4.1** protocol and supports **Apache Spark 3.5 and above** (the Spark Connect wire protocol is backward compatible across these releases).
170
+ The client is generated against the **Spark Connect 4.3** protocol and supports **Apache Spark 3.5 and above** (the Spark Connect wire protocol is backward compatible across these releases). APIs added in newer Spark releases (for example `DataFrame#zip` in 4.3 or `nearest_by_join` in 4.2) need a server of at least that version; see [Troubleshooting](https://hyukjinkwon.github.io/spark-connect-ruby/troubleshooting.html#version-compatibility) for the list. CI runs the integration specs against Spark 4.3.0, 4.2.0, 4.1.0, 4.0.0, and 3.5.5.
153
171
 
154
172
  ## Development
155
173
 
@@ -15,6 +15,22 @@ module SparkConnect
15
15
  Proto = SparkConnect::Proto
16
16
  C = Proto::Catalog
17
17
 
18
+ # PySpark-style camelCase aliases, defined at the end of the class.
19
+ CAMEL_ALIASES = {
20
+ currentCatalog: :current_catalog, setCurrentCatalog: :set_current_catalog, listCatalogs: :list_catalogs,
21
+ currentDatabase: :current_database, setCurrentDatabase: :set_current_database, listDatabases: :list_databases,
22
+ getDatabase: :get_database, listTables: :list_tables, getTable: :get_table, listViews: :list_views,
23
+ listFunctions: :list_functions, getFunction: :get_function, listColumns: :list_columns,
24
+ tableExists: :table_exists, databaseExists: :database_exists, functionExists: :function_exists,
25
+ dropTempView: :drop_temp_view, dropGlobalTempView: :drop_global_temp_view, isCached: :cached?,
26
+ cacheTable: :cache_table, uncacheTable: :uncache_table, clearCache: :clear_cache, refreshTable: :refresh_table,
27
+ refreshByPath: :refresh_by_path, recoverPartitions: :recover_partitions, createTable: :create_table,
28
+ createExternalTable: :create_external_table, dropTable: :drop_table, dropView: :drop_view,
29
+ createDatabase: :create_database, dropDatabase: :drop_database, listPartitions: :list_partitions,
30
+ getTableProperties: :get_table_properties, getCreateTableString: :get_create_table_string,
31
+ truncateTable: :truncate_table, analyzeTable: :analyze_table,
32
+ }.freeze
33
+
18
34
  # @param session [SparkSession]
19
35
  def initialize(session)
20
36
  @session = session
@@ -30,9 +46,12 @@ module SparkConnect
30
46
  run(C.new(set_current_catalog: Proto::SetCurrentCatalog.new(catalog_name: name.to_s)))
31
47
  end
32
48
 
33
- # @return [Array<Row>] all catalogs.
34
- def list_catalogs
35
- rows(C.new(list_catalogs: Proto::ListCatalogs.new))
49
+ # @param pattern [String, nil] a SQL LIKE pattern the names must match.
50
+ # @return [Array<Row>] the catalogs (`name`, `description`).
51
+ def list_catalogs(pattern = nil)
52
+ lc = Proto::ListCatalogs.new
53
+ lc.pattern = pattern if pattern
54
+ rows(C.new(list_catalogs: lc))
36
55
  end
37
56
 
38
57
  # @return [String] the current default database.
@@ -45,26 +64,70 @@ module SparkConnect
45
64
  run(C.new(set_current_database: Proto::SetCurrentDatabase.new(db_name: name.to_s)))
46
65
  end
47
66
 
48
- # @return [Array<Row>] all databases.
49
- def list_databases
50
- rows(C.new(list_databases: Proto::ListDatabases.new))
67
+ # @param pattern [String, nil] a SQL LIKE pattern the names must match.
68
+ # @return [Array<Row>] the databases (`name`, `catalog`, `description`, `locationUri`).
69
+ def list_databases(pattern = nil)
70
+ ld = Proto::ListDatabases.new
71
+ ld.pattern = pattern if pattern
72
+ rows(C.new(list_databases: ld))
73
+ end
74
+
75
+ # @param db_name [String] the database (may be catalog-qualified).
76
+ # @return [Row] the database (`name`, `catalog`, `description`, `locationUri`).
77
+ def get_database(db_name)
78
+ rows(C.new(get_database: Proto::GetDatabase.new(db_name: db_name.to_s))).first
51
79
  end
52
80
 
53
81
  # @param db_name [String, nil] restrict to a database.
54
- # @return [Array<Row>] tables (and views).
55
- def list_tables(db_name = nil)
82
+ # @param pattern [String, nil] a SQL LIKE pattern the names must match.
83
+ # @return [Array<Row>] tables and views (`name`, `catalog`, `namespace`,
84
+ # `description`, `tableType`, `isTemporary`).
85
+ def list_tables(db_name = nil, pattern = nil)
56
86
  lt = Proto::ListTables.new
57
87
  lt.db_name = db_name if db_name
88
+ lt.pattern = pattern if pattern
58
89
  rows(C.new(list_tables: lt))
59
90
  end
60
91
 
92
+ # @param table_name [String] a (possibly qualified) table or view name.
93
+ # @return [Row] the table (`name`, `catalog`, `namespace`, `description`,
94
+ # `tableType`, `isTemporary`).
95
+ def get_table(table_name, db_name = nil)
96
+ gt = Proto::GetTable.new(table_name: table_name.to_s)
97
+ gt.db_name = db_name if db_name
98
+ rows(C.new(get_table: gt)).first
99
+ end
100
+
101
+ # Views (not tables) in a database (Spark 4.2+).
102
+ #
103
+ # @param db_name [String, nil] the database (current database when nil).
104
+ # @param pattern [String, nil] a SQL LIKE pattern the names must match.
105
+ # @return [Array<Row>] like {#list_tables}.
106
+ def list_views(db_name = nil, pattern = nil)
107
+ db_name = current_database if pattern && db_name.nil?
108
+ lv = Proto::ListViews.new
109
+ lv.db_name = db_name if db_name
110
+ lv.pattern = pattern if pattern
111
+ rows(C.new(list_views: lv))
112
+ end
113
+
61
114
  # @return [Array<Row>] functions registered in the catalog.
62
- def list_functions(db_name = nil)
115
+ def list_functions(db_name = nil, pattern = nil)
63
116
  lf = Proto::ListFunctions.new
64
117
  lf.db_name = db_name if db_name
118
+ lf.pattern = pattern if pattern
65
119
  rows(C.new(list_functions: lf))
66
120
  end
67
121
 
122
+ # @param function_name [String] a (possibly qualified) function name.
123
+ # @return [Row] the function (`name`, `catalog`, `namespace`, `description`,
124
+ # `className`, `isTemporary`).
125
+ def get_function(function_name, db_name = nil)
126
+ gf = Proto::GetFunction.new(function_name: function_name.to_s)
127
+ gf.db_name = db_name if db_name
128
+ rows(C.new(get_function: gf)).first
129
+ end
130
+
68
131
  # @param table_name [String]
69
132
  # @return [Array<Row>] the columns of a table.
70
133
  def list_columns(table_name, db_name = nil)
@@ -106,10 +169,15 @@ module SparkConnect
106
169
  def cached?(table_name)
107
170
  scalar(C.new(is_cached: Proto::IsCached.new(table_name: table_name.to_s))) == true
108
171
  end
172
+ alias is_cached cached?
109
173
 
110
- # Cache a table in memory. @return [void]
111
- def cache_table(table_name)
112
- run(C.new(cache_table: Proto::CacheTable.new(table_name: table_name.to_s)))
174
+ # Cache a table (in memory and on disk by default).
175
+ # @param storage_level [StorageLevel, nil]
176
+ # @return [void]
177
+ def cache_table(table_name, storage_level = nil)
178
+ ct = Proto::CacheTable.new(table_name: table_name.to_s)
179
+ ct.storage_level = storage_level.to_proto if storage_level
180
+ run(C.new(cache_table: ct))
113
181
  end
114
182
 
115
183
  # Remove a table from the cache. @return [void]
@@ -127,11 +195,86 @@ module SparkConnect
127
195
  run(C.new(refresh_table: Proto::RefreshTable.new(table_name: table_name.to_s)))
128
196
  end
129
197
 
198
+ # Invalidate and refresh cached data/metadata of everything under `path`.
199
+ # @return [void]
200
+ def refresh_by_path(path)
201
+ run(C.new(refresh_by_path: Proto::RefreshByPath.new(path: path.to_s)))
202
+ end
203
+
130
204
  # Recover all partitions of a table. @return [void]
131
205
  def recover_partitions(table_name)
132
206
  run(C.new(recover_partitions: Proto::RecoverPartitions.new(table_name: table_name.to_s)))
133
207
  end
134
208
 
209
+ # ---- DDL and table maintenance (Spark 4.2+) -------------------------------
210
+
211
+ # Drop a persistent table (not a temporary view; see {#drop_temp_view}).
212
+ #
213
+ # @param if_exists [Boolean] do not fail when the table does not exist.
214
+ # @param purge [Boolean] skip the trash (where supported).
215
+ # @return [void]
216
+ def drop_table(table_name, if_exists: false, purge: false)
217
+ run(C.new(drop_table: Proto::DropTable.new(table_name: table_name.to_s, if_exists: if_exists, purge: purge)))
218
+ end
219
+
220
+ # Drop a persistent view.
221
+ # @return [void]
222
+ def drop_view(view_name, if_exists: false)
223
+ run(C.new(drop_view: Proto::DropView.new(view_name: view_name.to_s, if_exists: if_exists)))
224
+ end
225
+
226
+ # Create a database (namespace).
227
+ #
228
+ # @param db_name [String] may be catalog-qualified / multi-part.
229
+ # @param if_not_exists [Boolean]
230
+ # @param properties [Hash{String=>String}] e.g. `comment`, `location`.
231
+ # @return [void]
232
+ def create_database(db_name, if_not_exists: false, properties: {})
233
+ cd = Proto::CreateDatabase.new(db_name: db_name.to_s, if_not_exists: if_not_exists, properties: stringify(properties))
234
+ run(C.new(create_database: cd))
235
+ end
236
+
237
+ # Drop a database (namespace).
238
+ #
239
+ # @param cascade [Boolean] also drop its tables, views, and functions.
240
+ # @return [void]
241
+ def drop_database(db_name, if_exists: false, cascade: false)
242
+ run(C.new(drop_database: Proto::DropDatabase.new(db_name: db_name.to_s, if_exists: if_exists, cascade: cascade)))
243
+ end
244
+
245
+ # The partitions of a table, like `SHOW PARTITIONS`.
246
+ # @return [Array<Row>] one Row with a `partition` String (e.g. `"p=1"`) per partition.
247
+ def list_partitions(table_name)
248
+ rows(C.new(list_partitions: Proto::ListPartitions.new(table_name: table_name.to_s)))
249
+ end
250
+
251
+ # The properties of a table or view, like `SHOW TBLPROPERTIES`.
252
+ # @return [Hash{String=>String}]
253
+ def get_table_properties(table_name)
254
+ rows(C.new(get_table_properties: Proto::GetTableProperties.new(table_name: table_name.to_s))).to_h { |r| [r[0], r[1]] }
255
+ end
256
+
257
+ # The `SHOW CREATE TABLE` DDL of a table or view.
258
+ # @param as_serde [Boolean] request Hive SerDe DDL where applicable.
259
+ # @return [String]
260
+ def get_create_table_string(table_name, as_serde: false)
261
+ gcts = Proto::GetCreateTableString.new(table_name: table_name.to_s, as_serde: as_serde)
262
+ scalar(C.new(get_create_table_string: gcts)).to_s
263
+ end
264
+
265
+ # Remove all rows from a table (not supported for views).
266
+ # @return [void]
267
+ def truncate_table(table_name)
268
+ run(C.new(truncate_table: Proto::TruncateTable.new(table_name: table_name.to_s)))
269
+ end
270
+
271
+ # Compute table statistics, like `ANALYZE TABLE ... COMPUTE STATISTICS`.
272
+ # @param no_scan [Boolean] only compute size statistics (`NOSCAN`).
273
+ # @return [void]
274
+ def analyze_table(table_name, no_scan: false)
275
+ run(C.new(analyze_table: Proto::AnalyzeTable.new(table_name: table_name.to_s, no_scan: no_scan)))
276
+ end
277
+
135
278
  # Create a managed table and return a {DataFrame} over it.
136
279
  #
137
280
  # @param table_name [String]
@@ -163,6 +306,8 @@ module SparkConnect
163
306
  @session.table(table_name.to_s)
164
307
  end
165
308
 
309
+ CAMEL_ALIASES.each { |camel, snake| alias_method camel, snake }
310
+
166
311
  private
167
312
 
168
313
  def stringify(options)