@@ -41,6 +41,8 @@ def get_hive_fields():
4141 }
4242 _load_symbol ("common/utils/utils.py" , "equals_ignore_case" , namespace )
4343 _load_symbol ("apps/datasource/models/datasource.py" , "ColumnSchema" , namespace )
44+ _load_symbol ("apps/db/db.py" , "is_spark_thrift" , namespace )
45+ _load_symbol ("apps/db/db.py" , "parse_hive_field_rows" , namespace )
4446 get_fields = _load_symbol ("apps/db/db.py" , "get_fields" , namespace )
4547
4648 def discover (rows ):
@@ -122,3 +124,106 @@ def test_extra_driver_columns_remain_supported(get_hive_fields):
122124@pytest .mark .parametrize ("rows" , [[], [("" , "" , "" ), ("# col_name" , "data_type" , "comment" )]])
123125def test_no_real_columns_returns_empty_list (get_hive_fields , rows ):
124126 assert get_hive_fields (rows ) == []
127+
128+
129+ # --- Spark Thrift Server support ---
130+
131+
132+ @pytest .fixture
133+ def get_hive_spark_fields ():
134+ cursor = Mock ()
135+ connection = SimpleNamespace (cursor = lambda : nullcontext (cursor ))
136+ pool = SimpleNamespace (connection = lambda : nullcontext (connection ))
137+ engine_probe = Mock (return_value = True )
138+ namespace = {
139+ "json" : json ,
140+ "CoreDatasource" : SimpleNamespace ,
141+ "DatasourceConf" : SimpleNamespace ,
142+ "aes_decrypt" : lambda value : value ,
143+ "DB" : SimpleNamespace (
144+ get_db = lambda _ : SimpleNamespace (connect_type = "driver" )
145+ ),
146+ "ConnectType" : SimpleNamespace (sqlalchemy = "sqlalchemy" ),
147+ "get_driver_pool" : lambda _ : pool ,
148+ "get_field_sql" : lambda * args : ("DESCRIBE sample" , None , None ),
149+ "is_spark_thrift" : engine_probe ,
150+ }
151+ _load_symbol ("common/utils/utils.py" , "equals_ignore_case" , namespace )
152+ _load_symbol ("apps/datasource/models/datasource.py" , "ColumnSchema" , namespace )
153+ _load_symbol ("apps/db/db.py" , "parse_hive_field_rows" , namespace )
154+ get_fields = _load_symbol ("apps/db/db.py" , "get_fields" , namespace )
155+
156+ def discover (rows ):
157+ cursor .fetchall .return_value = rows
158+ fields = get_fields (SimpleNamespace (type = "hive" , configuration = "{}" ), "sample" )
159+ return [(field .fieldName , field .fieldType , field .fieldComment ) for field in fields ]
160+
161+ return discover
162+
163+
164+ def test_spark_describe_skips_col_name_header_row (get_hive_spark_fields ):
165+ # Spark Thrift Server returns the (col_name, data_type, comment) header as data.
166+ rows = [("col_name" , "data_type" , "comment" ),
167+ ("id" , "bigint" , None ),
168+ ("dt" , "string" , "partition date" )]
169+ assert get_hive_spark_fields (rows ) == [
170+ ("id" , "bigint" , None ),
171+ ("dt" , "string" , "partition date" ),
172+ ]
173+
174+
175+ def test_spark_describe_with_partition_section (get_hive_spark_fields ):
176+ rows = [
177+ ("col_name" , "data_type" , "comment" ),
178+ ("id" , "bigint" , None ),
179+ ("# Partition Information" , "" , "" ),
180+ ("# col_name" , "data_type" , "comment" ),
181+ ("dt" , "string" , "partition date" ),
182+ ]
183+ assert get_hive_spark_fields (rows ) == [
184+ ("id" , "bigint" , None ),
185+ ("dt" , "string" , "partition date" ),
186+ ]
187+
188+
189+ # --- SHOW TABLES (parse_hive_table_rows) ---
190+
191+
192+ @pytest .fixture
193+ def get_hive_tables ():
194+ namespace = {"DatasourceConf" : SimpleNamespace }
195+ _load_symbol ("apps/db/db.py" , "parse_hive_table_rows" , namespace )
196+ _load_symbol ("common/utils/utils.py" , "equals_ignore_case" , namespace )
197+ _load_symbol ("apps/db/db.py" , "is_spark_thrift" , namespace )
198+ return namespace ["parse_hive_table_rows" ]
199+
200+
201+ def test_hs2_show_tables_single_column (get_hive_tables ):
202+ rows = [("orders" ,), ("users" ,), ("orders" ,)]
203+ assert get_hive_tables (rows , False ) == [("orders" , "" ), ("users" , "" )]
204+
205+
206+ def test_spark_show_tables_three_columns_with_header (get_hive_tables ):
207+ rows = [
208+ ("default" , "tableName" , "isTemporary" ), # header row returned as data
209+ ("default" , "orders" , "false" ),
210+ ("default" , "users" , "false" ),
211+ ("default" , "orders" , "false" ),
212+ ]
213+ assert get_hive_tables (rows , True ) == [("orders" , "" ), ("users" , "" )]
214+
215+
216+ def test_spark_show_tables_without_header (get_hive_tables ):
217+ rows = [("default" , "sales" , "false" ), ("default" , "inventory" , "false" )]
218+ assert get_hive_tables (rows , True ) == [("sales" , "" ), ("inventory" , "" )]
219+
220+
221+ def test_spark_show_tables_ignores_database_column_and_handles_temp_views (get_hive_tables ):
222+ # Spark 2.4+/3.x/4.x always returns (database, tableName, isTemporary);
223+ # take the second column and tolerate an empty database (temp views).
224+ rows = [
225+ ("salesdb" , "orders" , "false" ),
226+ ("" , "temp_view" , "true" ),
227+ ("salesdb" , "orders" , "false" ),
228+ ]
229+ assert get_hive_tables (rows , True ) == [("orders" , "" ), ("temp_view" , "" )]
0 commit comments