Skip to content

Commit 56fc221

Browse files
author
Peng Ren
committed
Support collection name having hyphen in it
1 parent fea3308 commit 56fc221

13 files changed

Lines changed: 2790 additions & 2509 deletions

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,10 @@ PyMongoSQL can be used as a database driver in Apache Superset for querying and
536536

537537
This allows seamless integration between MongoDB data and Superset's BI capabilities without requiring data migration to traditional SQL databases.
538538

539+
**Important Note on Collection Names:**
540+
541+
When using collection names containing special characters (`.`, `-`, `:`), you must wrap them in double quotes to prevent Superset's SQL parser from incorrectly interpreting them.
542+
539543
## Contributing
540544

541545
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

pymongosql/sql/partiql/PartiQLLexer.g4

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ CURRENT_TIMESTAMP: 'CURRENT_TIMESTAMP';
7777
CURRENT_USER: 'CURRENT_USER';
7878
CURSOR: 'CURSOR';
7979
DATE: 'DATE';
80+
DATETIME: 'DATETIME';
8081
DEALLOCATE: 'DEALLOCATE';
8182
DEC: 'DEC';
8283
DECIMAL: 'DECIMAL';
@@ -370,7 +371,7 @@ LITERAL_DECIMAL:
370371
;
371372

372373
IDENTIFIER
373-
: [A-Z$_][A-Z0-9$_]*;
374+
: [A-Z$_][A-Z0-9$_-]*;
374375

375376
IDENTIFIER_QUOTED
376377
: '"' ( ('""') | ~('"') )* '"';

pymongosql/sql/partiql/PartiQLLexer.py

Lines changed: 1452 additions & 1448 deletions
Large diffs are not rendered by default.

pymongosql/sql/partiql/PartiQLParser.g4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ functionCall
723723

724724
// SQL-99 10.4 � <routine name> ::= [ <schema name> <period> ] <qualified identifier>
725725
functionName
726-
: (qualifier+=symbolPrimitive PERIOD)* name=( CHAR_LENGTH | CHARACTER_LENGTH | OCTET_LENGTH | BIT_LENGTH | UPPER | LOWER | SIZE | EXISTS | COUNT ) # FunctionNameReserved
726+
: (qualifier+=symbolPrimitive PERIOD)* name=( CHAR_LENGTH | CHARACTER_LENGTH | OCTET_LENGTH | BIT_LENGTH | UPPER | LOWER | SIZE | EXISTS | COUNT | DATE | DATETIME | SUBSTRING | REPLACE | TRIM ) # FunctionNameReserved
727727
| (qualifier+=symbolPrimitive PERIOD)* name=symbolPrimitive # FunctionNameSymbol
728728
;
729729

pymongosql/sql/partiql/PartiQLParser.py

Lines changed: 1066 additions & 1053 deletions
Large diffs are not rendered by default.

pymongosql/sql/partiql/PartiQLParserListener.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated from PartiQLParser.g4 by ANTLR 4.13.0
1+
# Generated from PartiQLParser.g4 by ANTLR 4.13.1
22
from antlr4 import *
33
if "." in __name__:
44
from .PartiQLParser import PartiQLParser

pymongosql/sql/partiql/PartiQLParserVisitor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated from PartiQLParser.g4 by ANTLR 4.13.0
1+
# Generated from PartiQLParser.g4 by ANTLR 4.13.1
22
from antlr4 import *
33
if "." in __name__:
44
from .PartiQLParser import PartiQLParser

pymongosql/sql/query_handler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,8 @@ def handle_visitor(self, ctx: PartiQLParser.FromClauseContext, parse_result: "Qu
244244

245245
# Regular collection reference
246246
table_text = ctx.tableReference().getText()
247-
collection_name = table_text
247+
# Strip surrounding quotes from collection name (e.g., "user.accounts" -> user.accounts)
248+
collection_name = re.sub(r'^"([^"]+)"$', r"\1", table_text)
248249
parse_result.collection = collection_name
249250
_logger.debug(f"Parsed regular collection: {collection_name}")
250251
return collection_name

tests/data/user-orders.json

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
[
2+
{
3+
"_id": "uo1",
4+
"user_id": "1",
5+
"order_id": "ord1",
6+
"order_priority": "normal",
7+
"notification_sent": true,
8+
"user_notes": "Regular customer - expedite if possible",
9+
"relationship_created_at": {"$date": "2023-12-01T10:30:00Z"},
10+
"customer_type": "premium",
11+
"follow_up_required": false
12+
},
13+
{
14+
"_id": "uo2",
15+
"user_id": "2",
16+
"order_id": "ord2",
17+
"order_priority": "high",
18+
"notification_sent": true,
19+
"user_notes": "First time buyer",
20+
"relationship_created_at": {"$date": "2023-12-10T14:22:00Z"},
21+
"customer_type": "standard",
22+
"follow_up_required": true
23+
},
24+
{
25+
"_id": "uo3",
26+
"user_id": "3",
27+
"order_id": "ord3",
28+
"order_priority": "urgent",
29+
"notification_sent": true,
30+
"user_notes": "VIP customer - priority handling",
31+
"relationship_created_at": {"$date": "2023-11-25T09:15:00Z"},
32+
"customer_type": "vip",
33+
"follow_up_required": false
34+
},
35+
{
36+
"_id": "uo4",
37+
"user_id": "1",
38+
"order_id": "ord15",
39+
"order_priority": "normal",
40+
"notification_sent": true,
41+
"user_notes": "Repeat order",
42+
"relationship_created_at": {"$date": "2023-12-20T11:45:00Z"},
43+
"customer_type": "premium",
44+
"follow_up_required": false
45+
},
46+
{
47+
"_id": "uo5",
48+
"user_id": "4",
49+
"order_id": "ord4",
50+
"order_priority": "normal",
51+
"notification_sent": false,
52+
"user_notes": null,
53+
"relationship_created_at": {"$date": "2023-12-15T16:30:00Z"},
54+
"customer_type": "standard",
55+
"follow_up_required": false
56+
},
57+
{
58+
"_id": "uo6",
59+
"user_id": "5",
60+
"order_id": "ord5",
61+
"order_priority": "low",
62+
"notification_sent": true,
63+
"user_notes": "Seasonal customer",
64+
"relationship_created_at": {"$date": "2023-12-18T08:20:00Z"},
65+
"customer_type": "standard",
66+
"follow_up_required": true
67+
},
68+
{
69+
"_id": "uo7",
70+
"user_id": "2",
71+
"order_id": "ord20",
72+
"order_priority": "high",
73+
"notification_sent": true,
74+
"user_notes": "Special packaging requested",
75+
"relationship_created_at": {"$date": "2024-01-05T13:10:00Z"},
76+
"customer_type": "standard",
77+
"follow_up_required": false
78+
},
79+
{
80+
"_id": "uo8",
81+
"user_id": "3",
82+
"order_id": "ord25",
83+
"order_priority": "urgent",
84+
"notification_sent": true,
85+
"user_notes": "Rush delivery requested",
86+
"relationship_created_at": {"$date": "2024-01-10T10:00:00Z"},
87+
"customer_type": "vip",
88+
"follow_up_required": true
89+
},
90+
{
91+
"_id": "uo9",
92+
"user_id": "6",
93+
"order_id": "ord6",
94+
"order_priority": "normal",
95+
"notification_sent": true,
96+
"user_notes": "Corporate account",
97+
"relationship_created_at": {"$date": "2023-12-22T14:55:00Z"},
98+
"customer_type": "corporate",
99+
"follow_up_required": false
100+
},
101+
{
102+
"_id": "uo10",
103+
"user_id": "7",
104+
"order_id": "ord7",
105+
"order_priority": "normal",
106+
"notification_sent": false,
107+
"user_notes": "Email bounced - alternate contact needed",
108+
"relationship_created_at": {"$date": "2023-12-28T09:30:00Z"},
109+
"customer_type": "standard",
110+
"follow_up_required": true
111+
},
112+
{
113+
"_id": "uo11",
114+
"user_id": "1",
115+
"order_id": "ord30",
116+
"order_priority": "high",
117+
"notification_sent": true,
118+
"user_notes": "Loyalty program member",
119+
"relationship_created_at": {"$date": "2024-01-15T12:20:00Z"},
120+
"customer_type": "premium",
121+
"follow_up_required": false
122+
},
123+
{
124+
"_id": "uo12",
125+
"user_id": "8",
126+
"order_id": "ord8",
127+
"order_priority": "normal",
128+
"notification_sent": true,
129+
"user_notes": null,
130+
"relationship_created_at": {"$date": "2024-01-02T15:40:00Z"},
131+
"customer_type": "standard",
132+
"follow_up_required": false
133+
},
134+
{
135+
"_id": "uo13",
136+
"user_id": "9",
137+
"order_id": "ord9",
138+
"order_priority": "low",
139+
"notification_sent": true,
140+
"user_notes": "International shipping",
141+
"relationship_created_at": {"$date": "2024-01-08T11:15:00Z"},
142+
"customer_type": "standard",
143+
"follow_up_required": true
144+
},
145+
{
146+
"_id": "uo14",
147+
"user_id": "10",
148+
"order_id": "ord10",
149+
"order_priority": "urgent",
150+
"notification_sent": true,
151+
"user_notes": "Gift order - include gift message",
152+
"relationship_created_at": {"$date": "2024-01-12T16:50:00Z"},
153+
"customer_type": "premium",
154+
"follow_up_required": false
155+
},
156+
{
157+
"_id": "uo15",
158+
"user_id": "3",
159+
"order_id": "ord35",
160+
"order_priority": "urgent",
161+
"notification_sent": true,
162+
"user_notes": "VIP - anniversary order",
163+
"relationship_created_at": {"$date": "2024-01-20T10:30:00Z"},
164+
"customer_type": "vip",
165+
"follow_up_required": false
166+
}
167+
]

tests/run_test_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,8 @@ def setup_test_data():
263263
)
264264
db = client[MONGODB_DATABASE]
265265

266-
# List of all collections to handle
267-
collections = ["users", "products", "categories", "orders", "analytics", "departments", "suppliers"]
266+
# Get all collections from loaded test data (dynamic, no hardcoding)
267+
collections = list(test_data.keys())
268268

269269
# Clear existing data and insert new data for each collection
270270
for collection_name in collections:

0 commit comments

Comments
 (0)