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
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SparkConnect
4
+ # Built-in table-valued functions, returned by {SparkSession#tvf}. Mirrors
5
+ # PySpark's `spark.tvf` (`TableValuedFunction`). Arguments are {Column}s or
6
+ # column names; wrap literals with {Functions.lit}.
7
+ #
8
+ # @example
9
+ # spark.tvf.explode(F.array(F.lit(1), F.lit(2))).show
10
+ # spark.tvf.sql_keywords.limit(5).collect
11
+ class TableValuedFunction
12
+ Proto = SparkConnect::Proto
13
+
14
+ # @param session [SparkSession]
15
+ def initialize(session)
16
+ @session = session
17
+ end
18
+
19
+ # Same as {SparkSession#range}.
20
+ # @return [DataFrame]
21
+ def range(start, end_ = nil, step = 1, num_partitions = nil)
22
+ @session.range(start, end_, step, num_partitions)
23
+ end
24
+
25
+ %w[explode explode_outer inline inline_outer posexplode posexplode_outer variant_explode
26
+ variant_explode_outer].each do |name|
27
+ define_method(name) { |input| call(name, input) }
28
+ end
29
+
30
+ # @!method explode(collection)
31
+ # One row per element of an array (or entry of a map). @return [DataFrame]
32
+ # @!method explode_outer(collection)
33
+ # Like {#explode}, but a null/empty input yields one row of nulls. @return [DataFrame]
34
+ # @!method inline(input)
35
+ # One row per struct of an array of structs. @return [DataFrame]
36
+ # @!method inline_outer(input)
37
+ # Like {#inline}, keeping null/empty inputs. @return [DataFrame]
38
+ # @!method posexplode(collection)
39
+ # Like {#explode} with the element position. @return [DataFrame]
40
+ # @!method posexplode_outer(collection)
41
+ # Like {#posexplode}, keeping null/empty inputs. @return [DataFrame]
42
+ # @!method variant_explode(input)
43
+ # One row per element/field of a VARIANT array/object. @return [DataFrame]
44
+ # @!method variant_explode_outer(input)
45
+ # Like {#variant_explode}, keeping null/empty inputs. @return [DataFrame]
46
+
47
+ # The given fields of a JSON string, one column each.
48
+ # @return [DataFrame]
49
+ def json_tuple(input, *fields)
50
+ raise IllegalArgumentError, "json_tuple requires at least one field" if fields.empty?
51
+
52
+ call("json_tuple", input, *fields)
53
+ end
54
+
55
+ # Separate `fields` into `n` rows.
56
+ # @return [DataFrame]
57
+ def stack(n, *fields) = call("stack", n, *fields)
58
+
59
+ # @return [DataFrame] the collations supported by the server.
60
+ def collations = call("collations")
61
+
62
+ # @return [DataFrame] the SQL keywords of the server.
63
+ def sql_keywords = call("sql_keywords")
64
+
65
+ # @return [DataFrame] the Python worker logs (when enabled on the server).
66
+ def python_worker_logs = call("python_worker_logs")
67
+
68
+ private
69
+
70
+ def call(name, *args)
71
+ tvf = Proto::UnresolvedTableValuedFunction.new(
72
+ function_name: name, arguments: args.map { |a| Functions._col(a).to_expr }
73
+ )
74
+ relation = PlanBuilder.relation(@session, unresolved_table_valued_function: tvf)
75
+ DataFrame.new(@session, PlanBuilder.with_subquery_references(@session, relation))
76
+ end
77
+ end
78
+ end
@@ -149,6 +149,104 @@ module SparkConnect
149
149
  def to_proto = Types.wrap(timestamp_ntz: Proto::DataType::TimestampNTZ.new)
150
150
  end
151
151
 
152
+ # Shared behaviour of the nanosecond-capable timestamp types (Spark 4.3+,
153
+ # a preview behind `spark.sql.timestampNanosTypes.enabled`).
154
+ class TimestampNanosBase < DataType
155
+ MIN_PRECISION = 7
156
+ MAX_PRECISION = 9
157
+
158
+ # @return [Integer] fractional-second digits, 7..9 (9 = nanoseconds).
159
+ attr_reader :precision
160
+
161
+ def initialize(precision = MAX_PRECISION)
162
+ super()
163
+ unless precision.is_a?(Integer) && precision.between?(MIN_PRECISION, MAX_PRECISION)
164
+ raise IllegalArgumentError, "#{self.class.name.split('::').last} precision must be 7, 8, or 9, got #{precision.inspect}"
165
+ end
166
+
167
+ @precision = precision
168
+ end
169
+
170
+ def simple_string = "#{type_name}(#{precision})"
171
+ def json_value = simple_string
172
+ def ==(other) = other.instance_of?(self.class) && other.precision == precision
173
+ def hash = [self.class, precision].hash
174
+ end
175
+
176
+ # TIMESTAMP_NTZ(p): a zone-less local date-time with nanosecond-capable precision.
177
+ class TimestampNTZNanosType < TimestampNanosBase
178
+ def type_name = "timestamp_ntz"
179
+ def to_proto = Types.wrap(timestamp_ntz_nanos: Proto::DataType::TimestampNTZNanos.new(precision: precision))
180
+ end
181
+
182
+ # TIMESTAMP_LTZ(p): an instant with nanosecond-capable precision.
183
+ class TimestampLTZNanosType < TimestampNanosBase
184
+ def type_name = "timestamp_ltz"
185
+ def to_proto = Types.wrap(timestamp_ltz_nanos: Proto::DataType::TimestampLTZNanos.new(precision: precision))
186
+ end
187
+
188
+ # TIME(p): a time of day without a date or time zone (Spark 4.1+).
189
+ class TimeType < DataType
190
+ # @return [Integer] fractional-second digits, 0..6.
191
+ attr_reader :precision
192
+
193
+ def initialize(precision = 6)
194
+ super()
195
+ @precision = precision
196
+ end
197
+
198
+ def simple_string = "time(#{precision})"
199
+ def type_name = "time"
200
+ def json_value = simple_string
201
+ def to_proto = Types.wrap(time: Proto::DataType::Time.new(precision: precision))
202
+ def ==(other) = other.is_a?(TimeType) && other.precision == precision
203
+ def hash = [TimeType, precision].hash
204
+ end
205
+
206
+ # Shared behaviour of the spatial types (Spark 4.1+). An SRID of
207
+ # {MIXED_SRID} (`"ANY"`) allows values of any reference system.
208
+ class SpatialBase < DataType
209
+ MIXED_SRID = -1
210
+
211
+ # @return [Integer]
212
+ attr_reader :srid
213
+
214
+ def initialize(srid)
215
+ super()
216
+ @srid = srid.to_s.casecmp?("any") ? MIXED_SRID : Integer(srid)
217
+ end
218
+
219
+ def simple_string = srid == MIXED_SRID ? "#{type_name}(any)" : "#{type_name}(#{srid})"
220
+ def ==(other) = other.instance_of?(self.class) && other.srid == srid
221
+ def hash = [self.class, srid].hash
222
+
223
+ private
224
+
225
+ # The coordinate reference system id used by Spark's JSON schema form.
226
+ def crs
227
+ case srid
228
+ when MIXED_SRID then "SRID:ANY"
229
+ when 4326 then "OGC:CRS84"
230
+ when 0 then "SRID:0"
231
+ else "EPSG:#{srid}"
232
+ end
233
+ end
234
+ end
235
+
236
+ # GEOMETRY(srid): a planar (Cartesian) spatial value.
237
+ class GeometryType < SpatialBase
238
+ def type_name = "geometry"
239
+ def json_value = "geometry(#{crs})"
240
+ def to_proto = Types.wrap(geometry: Proto::DataType::Geometry.new(srid: srid))
241
+ end
242
+
243
+ # GEOGRAPHY(srid): a geodesic (spherical) spatial value.
244
+ class GeographyType < SpatialBase
245
+ def type_name = "geography"
246
+ def json_value = "geography(#{crs}, SPHERICAL)"
247
+ def to_proto = Types.wrap(geography: Proto::DataType::Geography.new(srid: srid))
248
+ end
249
+
152
250
  class VariantType < DataType
153
251
  def type_name = "variant"
154
252
  def to_proto = Types.wrap(variant: Proto::DataType::Variant.new)
@@ -254,6 +352,33 @@ module SparkConnect
254
352
  def to_proto = Types.wrap(calendar_interval: Proto::DataType::CalendarInterval.new)
255
353
  end
256
354
 
355
+ # A user-defined type, described by its class name(s) and the SQL type it
356
+ # is stored as. Values decode as their `sql_type`.
357
+ class UserDefinedType < DataType
358
+ attr_reader :jvm_class, :python_class, :sql_type
359
+
360
+ def initialize(sql_type, jvm_class: nil, python_class: nil)
361
+ super()
362
+ @sql_type = sql_type
363
+ @jvm_class = jvm_class
364
+ @python_class = python_class
365
+ end
366
+
367
+ def simple_string = sql_type ? sql_type.simple_string : "udt"
368
+ def type_name = "udt"
369
+
370
+ def to_proto
371
+ udt = Proto::DataType::UDT.new(type: "udt")
372
+ udt.jvm_class = jvm_class if jvm_class
373
+ udt.python_class = python_class if python_class
374
+ udt.sql_type = sql_type.to_proto if sql_type
375
+ Types.wrap(udt: udt)
376
+ end
377
+
378
+ def ==(other) = other.is_a?(UserDefinedType) && other.sql_type == sql_type && other.jvm_class == jvm_class
379
+ def hash = [UserDefinedType, sql_type, jvm_class].hash
380
+ end
381
+
257
382
  # An array type. `element_type` is the type of every element; `contains_null`
258
383
  # indicates whether the array may contain `null` values.
259
384
  class ArrayType < DataType
@@ -440,6 +565,21 @@ module SparkConnect
440
565
  def date = DateType.new
441
566
  def timestamp = TimestampType.new
442
567
  def timestamp_ntz = TimestampNTZType.new
568
+ # @param precision [Integer] fractional-second digits, 7..9 (Spark 4.3 preview).
569
+ # @return [TimestampNTZNanosType]
570
+ def timestamp_ntz_nanos(precision = 9) = TimestampNTZNanosType.new(precision)
571
+ # @param precision [Integer] fractional-second digits, 7..9 (Spark 4.3 preview).
572
+ # @return [TimestampLTZNanosType]
573
+ def timestamp_ltz_nanos(precision = 9) = TimestampLTZNanosType.new(precision)
574
+ # @param precision [Integer] fractional-second digits, 0..6 (Spark 4.1+).
575
+ # @return [TimeType]
576
+ def time(precision = 6) = TimeType.new(precision)
577
+ # @param srid [Integer, String] spatial reference id, or `"ANY"` (Spark 4.1+).
578
+ # @return [GeometryType]
579
+ def geometry(srid = 4326) = GeometryType.new(srid)
580
+ # @param srid [Integer, String] spatial reference id, or `"ANY"` (Spark 4.1+).
581
+ # @return [GeographyType]
582
+ def geography(srid = 4326) = GeographyType.new(srid)
443
583
  def variant = VariantType.new
444
584
  def decimal(precision = 10, scale = 0) = DecimalType.new(precision, scale)
445
585
  def array(element_type, contains_null: true) = ArrayType.new(element_type, contains_null: contains_null)
@@ -468,6 +608,15 @@ module SparkConnect
468
608
  when :date then DateType.new
469
609
  when :timestamp then TimestampType.new
470
610
  when :timestamp_ntz then TimestampNTZType.new
611
+ when :timestamp_ntz_nanos then TimestampNTZNanosType.new(sub.has_precision? ? sub.precision : 9)
612
+ when :timestamp_ltz_nanos then TimestampLTZNanosType.new(sub.has_precision? ? sub.precision : 9)
613
+ when :time then TimeType.new(sub.has_precision? ? sub.precision : 6)
614
+ when :geometry then GeometryType.new(sub.srid)
615
+ when :geography then GeographyType.new(sub.srid)
616
+ when :udt
617
+ UserDefinedType.new(sub.has_sql_type? ? from_proto(sub.sql_type) : nil,
618
+ jvm_class: sub.has_jvm_class? ? sub.jvm_class : nil,
619
+ python_class: sub.has_python_class? ? sub.python_class : nil)
471
620
  when :variant then VariantType.new
472
621
  when :calendar_interval then CalendarIntervalType.new
473
622
  when :day_time_interval then DayTimeIntervalType.new(sub.start_field || 0, sub.end_field || 3)
@@ -2,10 +2,10 @@
2
2
 
3
3
  module SparkConnect
4
4
  # The released version of the spark-connect gem.
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
 
7
7
  # The Apache Spark version whose Spark Connect protocol definitions this
8
8
  # client is generated against. The client aims to be wire-compatible with
9
9
  # Spark Connect servers of this major/minor line and newer.
10
- SPARK_VERSION = "4.1.0"
10
+ SPARK_VERSION = "4.3.0"
11
11
  end
@@ -31,6 +31,8 @@ module SparkConnect
31
31
  @sort_columns = []
32
32
  @bucket_cols = nil
33
33
  @num_buckets = nil
34
+ @clustering_columns = []
35
+ @schema_evolution = false
34
36
  end
35
37
 
36
38
  # @return [self] set the output format.
@@ -40,25 +42,48 @@ module SparkConnect
40
42
  end
41
43
 
42
44
  # @return [self] set the save mode (`:append`, `:overwrite`, `:ignore`,
43
- # `:error`).
45
+ # `:error`). `nil` keeps the current mode.
44
46
  def mode(save_mode)
47
+ return self if save_mode.nil?
48
+
45
49
  @mode = SAVE_MODES.fetch(save_mode.to_s.downcase.to_sym) do
46
50
  raise IllegalArgumentError, "Unknown save mode: #{save_mode}"
47
51
  end
48
52
  self
49
53
  end
50
54
 
51
- # @return [self] set a write option.
55
+ # @return [self] set a write option (a `nil` value is ignored).
52
56
  def option(key, value)
53
- @options[key.to_s] = value.to_s
57
+ @options[key.to_s] = value.to_s unless value.nil?
54
58
  self
55
59
  end
56
60
 
57
61
  # @return [self] set multiple write options.
58
62
  def options(opts)
59
- opts.each { |k, v| @options[k.to_s] = v.to_s }
63
+ opts.each { |k, v| option(k, v) }
64
+ self
65
+ end
66
+
67
+ # @return [self] cluster the output (e.g. liquid clustering) by these columns.
68
+ def cluster_by(*cols)
69
+ cols = cols.flatten.map(&:to_s)
70
+ raise IllegalArgumentError, "cluster_by needs one or more clustering columns" if cols.empty?
71
+
72
+ @clustering_columns = cols
73
+ self
74
+ end
75
+ alias clusterBy cluster_by
76
+
77
+ # Enable automatic schema evolution for this write (Spark 4.2+); the target
78
+ # table must declare the `AUTOMATIC_SCHEMA_EVOLUTION` capability. This is
79
+ # the Scala API's `withSchemaEvolution` (PySpark exposes it only on
80
+ # {MergeIntoWriter}).
81
+ # @return [self]
82
+ def with_schema_evolution
83
+ @schema_evolution = true
60
84
  self
61
85
  end
86
+ alias withSchemaEvolution with_schema_evolution
62
87
 
63
88
  # @return [self] partition the output by these columns.
64
89
  def partition_by(*cols)
@@ -115,13 +140,27 @@ module SparkConnect
115
140
  def csv(path) = format("csv").save(path)
116
141
  def orc(path) = format("orc").save(path)
117
142
  def text(path) = format("text").save(path)
143
+ # @return [void] convenience for `format("xml").save(path)` (set `option("rowTag", ...)`).
144
+ def xml(path) = format("xml").save(path)
145
+
146
+ # Write to a JDBC table.
147
+ #
148
+ # @param url [String]
149
+ # @param table [String]
150
+ # @param mode [Symbol, String, nil] the save mode.
151
+ # @param properties [Hash] connection properties (`user`, `password`, ...).
152
+ # @return [void]
153
+ def jdbc(url, table, mode = nil, properties = {})
154
+ format("jdbc").mode(mode).options(properties.transform_keys(&:to_s)).options("url" => url, "dbtable" => table).save
155
+ end
118
156
 
119
157
  private
120
158
 
121
159
  def base_operation
122
160
  op = WO.new(
123
161
  input: @df.relation, mode: @mode, options: @options,
124
- partitioning_columns: @partitioning_columns, sort_column_names: @sort_columns
162
+ partitioning_columns: @partitioning_columns, sort_column_names: @sort_columns,
163
+ clustering_columns: @clustering_columns, with_schema_evolution: @schema_evolution
125
164
  )
126
165
  op.source = @source if @source
127
166
  op.bucket_by = WO::BucketBy.new(bucket_column_names: @bucket_cols, num_buckets: @num_buckets) if @num_buckets
@@ -153,6 +192,8 @@ module SparkConnect
153
192
  @options = {}
154
193
  @table_properties = {}
155
194
  @partitioning = []
195
+ @clustering_columns = []
196
+ @schema_evolution = false
156
197
  end
157
198
 
158
199
  # @return [self] set the table provider/format.
@@ -161,9 +202,15 @@ module SparkConnect
161
202
  self
162
203
  end
163
204
 
164
- # @return [self] set a write option.
205
+ # @return [self] set a write option (a `nil` value is ignored).
165
206
  def option(key, value)
166
- @options[key.to_s] = value.to_s
207
+ @options[key.to_s] = value.to_s unless value.nil?
208
+ self
209
+ end
210
+
211
+ # @return [self] set multiple write options.
212
+ def options(opts)
213
+ opts.each { |k, v| option(k, v) }
167
214
  self
168
215
  end
169
216
 
@@ -172,12 +219,32 @@ module SparkConnect
172
219
  @table_properties[key.to_s] = value.to_s
173
220
  self
174
221
  end
222
+ alias tableProperty table_property
175
223
 
176
- # @return [self] partition by the given expressions/columns.
177
- def partition_by(*cols)
224
+ # Partition the table by columns or partition transforms (`F.years("ts")`,
225
+ # `F.bucket(4, "id")`, ...).
226
+ # @return [self]
227
+ def partitioned_by(*cols)
178
228
  @partitioning = cols.flatten.map { |c| (c.is_a?(Column) ? c : Functions.col(c.to_s)).to_expr }
179
229
  self
180
230
  end
231
+ alias partition_by partitioned_by
232
+ alias partitionedBy partitioned_by
233
+
234
+ # @return [self] cluster the table by these columns.
235
+ def cluster_by(*cols)
236
+ @clustering_columns = cols.flatten.map(&:to_s)
237
+ self
238
+ end
239
+ alias clusterBy cluster_by
240
+
241
+ # Enable automatic schema evolution for this write (Spark 4.2+).
242
+ # @return [self]
243
+ def with_schema_evolution
244
+ @schema_evolution = true
245
+ self
246
+ end
247
+ alias withSchemaEvolution with_schema_evolution
181
248
 
182
249
  # Create the table. @return [void]
183
250
  def create = run(:MODE_CREATE)
@@ -185,19 +252,22 @@ module SparkConnect
185
252
  def replace = run(:MODE_REPLACE)
186
253
  # Create or replace the table. @return [void]
187
254
  def create_or_replace = run(:MODE_CREATE_OR_REPLACE)
255
+ alias createOrReplace create_or_replace
188
256
  # Append rows. @return [void]
189
257
  def append = run(:MODE_APPEND)
190
258
  # Overwrite rows matching `condition`. @return [void]
191
259
  def overwrite(condition) = run(:MODE_OVERWRITE, overwrite_condition: Column.to_col(condition).to_expr)
192
260
  # Dynamically overwrite partitions. @return [void]
193
261
  def overwrite_partitions = run(:MODE_OVERWRITE_PARTITIONS)
262
+ alias overwritePartitions overwrite_partitions
194
263
 
195
264
  private
196
265
 
197
266
  def run(mode, overwrite_condition: nil)
198
267
  op = WO2.new(
199
268
  input: @df.relation, table_name: @table, mode: mode,
200
- options: @options, table_properties: @table_properties, partitioning_columns: @partitioning
269
+ options: @options, table_properties: @table_properties, partitioning_columns: @partitioning,
270
+ clustering_columns: @clustering_columns, with_schema_evolution: @schema_evolution
201
271
  )
202
272
  op.provider = @provider if @provider
203
273
  op.overwrite_condition = overwrite_condition if overwrite_condition
data/lib/spark_connect.rb CHANGED
@@ -9,6 +9,7 @@ require_relative "spark_connect/types"
9
9
  require_relative "spark_connect/row"
10
10
  require_relative "spark_connect/plan"
11
11
  require_relative "spark_connect/column"
12
+ require_relative "spark_connect/storage_level"
12
13
  require_relative "spark_connect/window"
13
14
  require_relative "spark_connect/functions"
14
15
  require_relative "spark_connect/arrow"
@@ -25,6 +26,9 @@ require_relative "spark_connect/streaming"
25
26
  require_relative "spark_connect/pipelines"
26
27
  require_relative "spark_connect/catalog"
27
28
  require_relative "spark_connect/data_frame"
29
+ require_relative "spark_connect/table_arg"
30
+ require_relative "spark_connect/merge"
31
+ require_relative "spark_connect/tvf"
28
32
  require_relative "spark_connect/session"
29
33
 
30
34
  # spark-connect is a pure-Ruby client for {https://spark.apache.org/docs/latest/spark-connect-overview.html
@@ -491,6 +491,12 @@ message ExecutePlanResponse {
491
491
  repeated Expression.Literal values = 2;
492
492
  repeated string keys = 3;
493
493
  int64 plan_id = 4;
494
+ // (Optional) The index of the root error in errors.
495
+ // The field will not be set if there are no errors.
496
+ optional int32 root_error_idx = 5;
497
+ // A list of errors that occurred while collecting the observed metrics.
498
+ // If the length is 0, it means no errors occurred.
499
+ repeated FetchErrorDetailsResponse.Error errors = 6;
494
500
  }
495
501
 
496
502
  message ResultComplete {
@@ -1213,11 +1219,94 @@ message CloneSessionResponse {
1213
1219
 
1214
1220
  // Session id of the new cloned session.
1215
1221
  string new_session_id = 3;
1216
-
1222
+
1217
1223
  // Server-side session ID of the new cloned session.
1218
1224
  string new_server_side_session_id = 4;
1219
1225
  }
1220
1226
 
1227
+ // Next ID: 6
1228
+ message GetStatusRequest {
1229
+ // (Required)
1230
+ //
1231
+ // The session_id specifies a Spark session for a user identified by user_context.user_id.
1232
+ // The id should be an UUID string of the format `00112233-4455-6677-8899-aabbccddeeff`
1233
+ string session_id = 1;
1234
+
1235
+ // (Required)
1236
+ //
1237
+ // user_context.user_id and session_id both identify a unique remote spark session on the
1238
+ // server side.
1239
+ UserContext user_context = 2;
1240
+
1241
+ // (Optional)
1242
+ //
1243
+ // Provides optional information about the client sending the request. This field
1244
+ // can be used for language or version specific information and is only intended for
1245
+ // logging purposes and will not be interpreted by the server.
1246
+ optional string client_type = 3;
1247
+
1248
+ // (Optional)
1249
+ //
1250
+ // Server-side generated idempotency key from the previous responses (if any). Server
1251
+ // can use this to validate that the server side session has not changed.
1252
+ optional string client_observed_server_side_session_id = 4;
1253
+
1254
+ // (Optional)
1255
+ //
1256
+ // Get status of operations in the session.
1257
+ optional OperationStatusRequest operation_status = 5;
1258
+
1259
+ // Extension point for custom status request types.
1260
+ repeated google.protobuf.Any extensions = 999;
1261
+
1262
+ message OperationStatusRequest {
1263
+ // Get status of operations with these operation_ids.
1264
+ // If unset or empty, returns status of all operations in the session.
1265
+ repeated string operation_ids = 1;
1266
+
1267
+ // Extension point for custom operation-level status requests.
1268
+ repeated google.protobuf.Any extensions = 999;
1269
+ }
1270
+ }
1271
+
1272
+ // Next ID: 4
1273
+ message GetStatusResponse {
1274
+ // Session id of the session for which the status was requested.
1275
+ string session_id = 1;
1276
+
1277
+ // Server-side generated idempotency key that the client can use to assert that the server side
1278
+ // session has not changed.
1279
+ string server_side_session_id = 2;
1280
+
1281
+ // Status information about requested operations.
1282
+ repeated OperationStatus operation_statuses = 3;
1283
+
1284
+ // Extension point for custom status response types.
1285
+ repeated google.protobuf.Any extensions = 999;
1286
+
1287
+ // Status information for a single operation.
1288
+ message OperationStatus {
1289
+ // The operation_id of the operation.
1290
+ string operation_id = 1;
1291
+
1292
+ // The current status of the operation.
1293
+ OperationState state = 2;
1294
+
1295
+ // Extension point for custom operation-level status fields.
1296
+ repeated google.protobuf.Any extensions = 999;
1297
+
1298
+ enum OperationState {
1299
+ OPERATION_STATE_UNSPECIFIED = 0;
1300
+ OPERATION_STATE_UNKNOWN = 1;
1301
+ OPERATION_STATE_RUNNING = 2;
1302
+ OPERATION_STATE_TERMINATING = 3;
1303
+ OPERATION_STATE_SUCCEEDED = 4;
1304
+ OPERATION_STATE_FAILED = 5;
1305
+ OPERATION_STATE_CANCELLED = 6;
1306
+ }
1307
+ }
1308
+ }
1309
+
1221
1310
  // Main interface for the SparkConnect service.
1222
1311
  service SparkConnectService {
1223
1312
 
@@ -1272,4 +1361,7 @@ service SparkConnectService {
1272
1361
  // The request can optionally specify a custom session ID for the cloned session (must be
1273
1362
  // a valid UUID). If not provided, a new UUID will be generated automatically.
1274
1363
  rpc CloneSession(CloneSessionRequest) returns (CloneSessionResponse) {}
1364
+
1365
+ // Get status information of different types.
1366
+ rpc GetStatus(GetStatusRequest) returns (GetStatusResponse) {}
1275
1367
  }
@@ -55,6 +55,16 @@ message Catalog {
55
55
  CurrentCatalog current_catalog = 24;
56
56
  SetCurrentCatalog set_current_catalog = 25;
57
57
  ListCatalogs list_catalogs = 26;
58
+ DropTable drop_table = 27;
59
+ DropView drop_view = 28;
60
+ CreateDatabase create_database = 29;
61
+ DropDatabase drop_database = 30;
62
+ ListPartitions list_partitions = 31;
63
+ ListViews list_views = 32;
64
+ GetTableProperties get_table_properties = 33;
65
+ GetCreateTableString get_create_table_string = 34;
66
+ TruncateTable truncate_table = 35;
67
+ AnalyzeTable analyze_table = 36;
58
68
  }
59
69
  }
60
70
 
@@ -241,3 +251,74 @@ message ListCatalogs {
241
251
  // (Optional) The pattern that the catalog name needs to match
242
252
  optional string pattern = 1;
243
253
  }
254
+
255
+ // See `spark.catalog.dropTable`
256
+ message DropTable {
257
+ // (Required)
258
+ string table_name = 1;
259
+ bool if_exists = 2;
260
+ bool purge = 3;
261
+ }
262
+
263
+ // See `spark.catalog.dropView`
264
+ message DropView {
265
+ // (Required)
266
+ string view_name = 1;
267
+ bool if_exists = 2;
268
+ }
269
+
270
+ // See `spark.catalog.createDatabase`
271
+ message CreateDatabase {
272
+ // (Required)
273
+ string db_name = 1;
274
+ bool if_not_exists = 2;
275
+ map<string, string> properties = 3;
276
+ }
277
+
278
+ // See `spark.catalog.dropDatabase`
279
+ message DropDatabase {
280
+ // (Required)
281
+ string db_name = 1;
282
+ bool if_exists = 2;
283
+ bool cascade = 3;
284
+ }
285
+
286
+ // See `spark.catalog.listPartitions`
287
+ message ListPartitions {
288
+ // (Required)
289
+ string table_name = 1;
290
+ }
291
+
292
+ // See `spark.catalog.listViews`
293
+ message ListViews {
294
+ // (Optional)
295
+ optional string db_name = 1;
296
+ // (Optional) The pattern that the view name needs to match
297
+ optional string pattern = 2;
298
+ }
299
+
300
+ // See `spark.catalog.getTableProperties`
301
+ message GetTableProperties {
302
+ // (Required)
303
+ string table_name = 1;
304
+ }
305
+
306
+ // See `spark.catalog.getCreateTableString`
307
+ message GetCreateTableString {
308
+ // (Required)
309
+ string table_name = 1;
310
+ bool as_serde = 2;
311
+ }
312
+
313
+ // See `spark.catalog.truncateTable`
314
+ message TruncateTable {
315
+ // (Required)
316
+ string table_name = 1;
317
+ }
318
+
319
+ // See `spark.catalog.analyzeTable`
320
+ message AnalyzeTable {
321
+ // (Required)
322
+ string table_name = 1;
323
+ bool no_scan = 2;
324
+ }
@@ -144,6 +144,9 @@ message WriteOperation {
144
144
  // (Optional) Columns used for clustering the table.
145
145
  repeated string clustering_columns = 10;
146
146
 
147
+ // (Optional) Whether schema evolution is enabled for the write.
148
+ bool with_schema_evolution = 11;
149
+
147
150
  message SaveTable {
148
151
  // (Required) The table name.
149
152
  string table_name = 1;
@@ -211,6 +214,9 @@ message WriteOperationV2 {
211
214
 
212
215
  // (Optional) Columns used for clustering the table.
213
216
  repeated string clustering_columns = 9;
217
+
218
+ // (Optional) Whether schema evolution is enabled for the write.
219
+ bool with_schema_evolution = 10;
214
220
  }
215
221
 
216
222
  // Starts write stream operation as streaming query. Query ID and Run ID of the streaming
@@ -232,6 +238,7 @@ message WriteStreamOperationStart {
232
238
  bool available_now = 6;
233
239
  bool once = 7;
234
240
  string continuous_checkpoint_interval = 8;
241
+ string real_time_batch_duration = 100;
235
242
  }
236
243
 
237
244
  string output_mode = 9;