@@ -950,3 +950,139 @@ def test_cli_date_combinations(self):
950950 # Should not fail on parameter validation
951951 stderr_output = result .stderr .decode ()
952952 assert "must be provided" not in stderr_output
953+
954+
955+ class TestPostgreSQLSourceQueryStringAlias :
956+ """
957+ Test suite for get_table_query_string_with_alias() method.
958+
959+ This addresses GitHub issue #5605: PostgreSQL requires all subqueries
960+ in FROM clauses to have aliases.
961+ """
962+
963+ def test_table_source_get_table_query_string (self ):
964+ """Test get_table_query_string() with table-based source"""
965+ source = PostgreSQLSource (
966+ name = "test_source" ,
967+ table = "my_schema.my_table" ,
968+ timestamp_field = "event_timestamp" ,
969+ )
970+ result = source .get_table_query_string ()
971+ assert result == "my_schema.my_table"
972+
973+ def test_query_source_get_table_query_string (self ):
974+ """Test get_table_query_string() with query-based source"""
975+ source = PostgreSQLSource (
976+ name = "test_source" ,
977+ query = "SELECT * FROM my_table WHERE active = true" ,
978+ timestamp_field = "event_timestamp" ,
979+ )
980+ result = source .get_table_query_string ()
981+ assert result == "(SELECT * FROM my_table WHERE active = true)"
982+
983+ def test_table_source_with_alias (self ):
984+ """Test get_table_query_string_with_alias() with table-based source returns table without alias"""
985+ source = PostgreSQLSource (
986+ name = "test_source" ,
987+ table = "my_schema.my_table" ,
988+ timestamp_field = "event_timestamp" ,
989+ )
990+ result = source .get_table_query_string_with_alias ()
991+ # Table sources don't need aliases
992+ assert result == "my_schema.my_table"
993+
994+ def test_query_source_with_default_alias (self ):
995+ """Test get_table_query_string_with_alias() with query-based source uses default alias"""
996+ source = PostgreSQLSource (
997+ name = "test_source" ,
998+ query = "SELECT * FROM my_table WHERE active = true" ,
999+ timestamp_field = "event_timestamp" ,
1000+ )
1001+ result = source .get_table_query_string_with_alias ()
1002+ assert result == "(SELECT * FROM my_table WHERE active = true) AS subquery"
1003+
1004+ def test_query_source_with_custom_alias (self ):
1005+ """Test get_table_query_string_with_alias() with custom alias"""
1006+ source = PostgreSQLSource (
1007+ name = "test_source" ,
1008+ query = "SELECT id, name FROM users" ,
1009+ timestamp_field = "event_timestamp" ,
1010+ )
1011+ result = source .get_table_query_string_with_alias (alias = "user_data" )
1012+ assert result == "(SELECT id, name FROM users) AS user_data"
1013+
1014+ def test_table_source_with_custom_alias_ignored (self ):
1015+ """Test get_table_query_string_with_alias() ignores alias for table-based sources"""
1016+ source = PostgreSQLSource (
1017+ name = "test_source" ,
1018+ table = "events" ,
1019+ timestamp_field = "event_timestamp" ,
1020+ )
1021+ result = source .get_table_query_string_with_alias (alias = "ignored_alias" )
1022+ # Alias should be ignored for table sources
1023+ assert result == "events"
1024+
1025+ def test_sql_query_with_alias_is_valid (self ):
1026+ """Test that SQL using get_table_query_string_with_alias() is syntactically valid"""
1027+ source = PostgreSQLSource (
1028+ name = "test_source" ,
1029+ query = "SELECT id, ts FROM raw_data" ,
1030+ timestamp_field = "ts" ,
1031+ )
1032+
1033+ # Construct a SQL query using the new method
1034+ entity_sql = f"SELECT id, ts FROM { source .get_table_query_string_with_alias ()} "
1035+
1036+ # Verify SQL is valid using sqlglot
1037+ parsed = sqlglot .parse (entity_sql , dialect = "postgres" )
1038+ assert len (parsed ) == 1
1039+ assert parsed [0 ] is not None
1040+
1041+ def test_sql_query_without_alias_fails_in_postgres (self ):
1042+ """Test that SQL using get_table_query_string() for query source produces invalid PostgreSQL
1043+
1044+ This demonstrates the issue that get_table_query_string_with_alias() fixes:
1045+ PostgreSQL requires all subqueries in FROM clauses to have aliases.
1046+ """
1047+ source = PostgreSQLSource (
1048+ name = "test_source" ,
1049+ query = "SELECT id, ts FROM raw_data" ,
1050+ timestamp_field = "ts" ,
1051+ )
1052+
1053+ # Using the old method (without alias) for query-based source
1054+ entity_sql_without_alias = (
1055+ f"SELECT id, ts FROM { source .get_table_query_string ()} "
1056+ )
1057+
1058+ # This produces: SELECT id, ts FROM (SELECT id, ts FROM raw_data)
1059+ # which is invalid in PostgreSQL (subquery needs alias)
1060+ # sqlglot is lenient and may parse it, but PostgreSQL would reject it
1061+ assert (
1062+ "AS" not in entity_sql_without_alias
1063+ ), "get_table_query_string() should not add alias"
1064+
1065+ # Using the new method (with alias) produces valid SQL
1066+ entity_sql_with_alias = (
1067+ f"SELECT id, ts FROM { source .get_table_query_string_with_alias ()} "
1068+ )
1069+ assert "AS subquery" in entity_sql_with_alias
1070+
1071+ def test_complex_query_with_alias (self ):
1072+ """Test get_table_query_string_with_alias() with complex nested query"""
1073+ complex_query = """
1074+ SELECT u.id, u.name, o.total
1075+ FROM users u
1076+ JOIN orders o ON u.id = o.user_id
1077+ WHERE o.created_at > '2023-01-01'
1078+ """
1079+ source = PostgreSQLSource (
1080+ name = "test_source" ,
1081+ query = complex_query ,
1082+ timestamp_field = "created_at" ,
1083+ )
1084+
1085+ result = source .get_table_query_string_with_alias (alias = "user_orders" )
1086+ assert result .startswith ("(" )
1087+ assert result .endswith (") AS user_orders" )
1088+ assert "SELECT u.id" in result
0 commit comments