|
10 | 10 | package net.sf.jsqlparser.expression; |
11 | 11 |
|
12 | 12 | import net.sf.jsqlparser.JSQLParserException; |
| 13 | +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; |
13 | 14 | import net.sf.jsqlparser.parser.CCJSqlParserUtil; |
14 | 15 | import net.sf.jsqlparser.statement.Statement; |
15 | 16 | import net.sf.jsqlparser.statement.select.PlainSelect; |
@@ -247,6 +248,34 @@ static Stream<Arguments> roundtripSqlProvider() { |
247 | 248 | "ALL + ORDER BY + SEPARATOR", |
248 | 249 | "SELECT my_agg(ALL col ORDER BY col SEPARATOR ',') FROM t"), |
249 | 250 |
|
| 251 | + // -- Multi-value keyword arguments (USING col1, col2, ...) --- |
| 252 | + // Oracle Data Mining functions use USING followed by a |
| 253 | + // comma-separated column list. |
| 254 | + |
| 255 | + Arguments.of( |
| 256 | + "Oracle PREDICTION with COST MODEL and USING column list", |
| 257 | + "SELECT PREDICTION(dt_sh_clas_sample COST MODEL USING cust_marital_status, education, household_size) FROM t"), |
| 258 | + |
| 259 | + Arguments.of( |
| 260 | + "Oracle PREDICTION in WHERE clause", |
| 261 | + "SELECT cust_gender, COUNT(*) AS cnt FROM mining_data_apply_v WHERE PREDICTION(dt_sh_clas_sample COST MODEL USING cust_marital_status, education, household_size) = 1 GROUP BY cust_gender ORDER BY cust_gender"), |
| 262 | + |
| 263 | + Arguments.of( |
| 264 | + "Oracle PREDICTION_PROBABILITY with USING", |
| 265 | + "SELECT PREDICTION_PROBABILITY(my_model USING col1, col2, col3) FROM t"), |
| 266 | + |
| 267 | + Arguments.of( |
| 268 | + "Oracle CLUSTER_ID with USING", |
| 269 | + "SELECT CLUSTER_ID(my_model USING col1, col2) FROM t"), |
| 270 | + |
| 271 | + Arguments.of( |
| 272 | + "USING with single column", |
| 273 | + "SELECT my_func(model USING col1) FROM t"), |
| 274 | + |
| 275 | + Arguments.of( |
| 276 | + "USING with many columns", |
| 277 | + "SELECT my_func(model USING a, b, c, d, e) FROM t"), |
| 278 | + |
250 | 279 | // -- Keyword arg in different SQL contexts ------------------- |
251 | 280 |
|
252 | 281 | Arguments.of( |
@@ -291,6 +320,31 @@ void testRoundtrip(String label, String sql) throws JSQLParserException { |
291 | 320 | + " reparsed: " + stmt2); |
292 | 321 | } |
293 | 322 |
|
| 323 | + // ==================================================================== |
| 324 | + // GitHub Issue #688 / #1257 - CONVERT(expr USING charset) |
| 325 | + // These were ParseExceptions before the generic keyword-arg tail. |
| 326 | + // ==================================================================== |
| 327 | + |
| 328 | + @Test |
| 329 | + void testIssue688_ConvertUsingGbk() throws JSQLParserException { |
| 330 | + // Exact SQL from issue #688 — was a ParseException before |
| 331 | + String sql = "SELECT * FROM a ORDER BY CONVERT(a.name USING gbk) DESC"; |
| 332 | + Statement stmt = CCJSqlParserUtil.parse(sql); |
| 333 | + assertNotNull(stmt); |
| 334 | + // Roundtrip |
| 335 | + String deparsed = stmt.toString(); |
| 336 | + assertEquals(deparsed, CCJSqlParserUtil.parse(deparsed).toString()); |
| 337 | + } |
| 338 | + |
| 339 | + @Test |
| 340 | + void testIssue1257_ConvertUsingGBK() throws JSQLParserException { |
| 341 | + // Exact SQL from issue #1257 |
| 342 | + String sql = |
| 343 | + "SELECT id, name FROM tbl_template WHERE name LIKE ? ORDER BY CONVERT(name USING GBK) ASC"; |
| 344 | + Statement stmt = CCJSqlParserUtil.parse(sql); |
| 345 | + assertNotNull(stmt); |
| 346 | + } |
| 347 | + |
294 | 348 | // ==================================================================== |
295 | 349 | // GROUP_CONCAT migration - now parsed as Function, not MySQLGroupConcat |
296 | 350 | // ==================================================================== |
@@ -388,6 +442,31 @@ void testMultipleKeywordArguments() throws JSQLParserException { |
388 | 442 | assertEquals("'utf8'", kwArgs.get(1).getExpression().toString()); |
389 | 443 | } |
390 | 444 |
|
| 445 | + @Test |
| 446 | + void testMultiValueKeywordArgument_OraclePrediction() throws JSQLParserException { |
| 447 | + String sql = "SELECT PREDICTION(my_model COST MODEL USING col1, col2, col3) FROM t"; |
| 448 | + Statement stmt = CCJSqlParserUtil.parse(sql); |
| 449 | + Function func = extractFirstFunction(stmt); |
| 450 | + |
| 451 | + assertNotNull(func); |
| 452 | + assertEquals("PREDICTION", func.getName()); |
| 453 | + |
| 454 | + List<Function.KeywordArgument> kwArgs = func.getKeywordArguments(); |
| 455 | + assertNotNull(kwArgs); |
| 456 | + assertEquals(2, kwArgs.size()); |
| 457 | + |
| 458 | + // COST MODEL — single value, unwrapped to Column |
| 459 | + assertEquals("COST", kwArgs.get(0).getKeyword().toUpperCase()); |
| 460 | + assertEquals("MODEL", kwArgs.get(0).getExpression().toString()); |
| 461 | + |
| 462 | + // USING col1, col2, col3 — multi-value, kept as ExpressionList |
| 463 | + assertEquals("USING", kwArgs.get(1).getKeyword().toUpperCase()); |
| 464 | + Expression usingExpr = kwArgs.get(1).getExpression(); |
| 465 | + assertInstanceOf(ExpressionList.class, |
| 466 | + usingExpr, "Multi-value keyword arg should be an ExpressionList"); |
| 467 | + assertEquals("col1, col2, col3", usingExpr.toString()); |
| 468 | + } |
| 469 | + |
391 | 470 | @Test |
392 | 471 | void testGetKeywordArgumentValue() throws JSQLParserException { |
393 | 472 | String sql = "SELECT my_agg(col SEPARATOR ',' ENCODING 'utf8') FROM t"; |
|
0 commit comments