Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/grammar/spark/SparkSqlParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ ifExists
;

lateralView
: KW_LATERAL KW_VIEW (KW_OUTER)? viewName LEFT_PAREN (expression (COMMA expression)*)? RIGHT_PAREN tableAlias (
: KW_LATERAL KW_VIEW (KW_OUTER)? functionName LEFT_PAREN (expression (COMMA expression)*)? RIGHT_PAREN tableAlias (
KW_AS? colName+=identifier (COMMA colName+=identifier)*
)?
;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/spark/SparkSqlParser.interp

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/lib/spark/SparkSqlParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10924,7 +10924,7 @@ export class SparkSqlParser extends SQLParserBase {
break;
}
this.state = 2478;
this.viewName();
this.functionName();
this.state = 2479;
this.match(SparkSqlParser.LEFT_PAREN);
this.state = 2488;
Expand Down Expand Up @@ -19676,7 +19676,7 @@ export class SparkSqlParser extends SQLParserBase {
105,0,0,2469,165,1,0,0,0,2470,2471,5,137,0,0,2471,2472,5,105,0,0,
2472,167,1,0,0,0,2473,2474,5,158,0,0,2474,2476,5,338,0,0,2475,2477,
5,211,0,0,2476,2475,1,0,0,0,2476,2477,1,0,0,0,2477,2478,1,0,0,0,
2478,2479,3,76,38,0,2479,2488,5,2,0,0,2480,2485,3,254,127,0,2481,
2478,2479,3,344,172,0,2479,2488,5,2,0,0,2480,2485,3,254,127,0,2481,
2482,5,4,0,0,2482,2484,3,254,127,0,2483,2481,1,0,0,0,2484,2487,1,
0,0,0,2485,2483,1,0,0,0,2485,2486,1,0,0,0,2486,2489,1,0,0,0,2487,
2485,1,0,0,0,2488,2480,1,0,0,0,2488,2489,1,0,0,0,2489,2490,1,0,0,
Expand Down Expand Up @@ -28874,8 +28874,8 @@ export class LateralViewContext extends antlr.ParserRuleContext {
public KW_VIEW(): antlr.TerminalNode {
return this.getToken(SparkSqlParser.KW_VIEW, 0)!;
}
public viewName(): ViewNameContext {
return this.getRuleContext(0, ViewNameContext)!;
public functionName(): FunctionNameContext {
return this.getRuleContext(0, FunctionNameContext)!;
}
public LEFT_PAREN(): antlr.TerminalNode {
return this.getToken(SparkSqlParser.LEFT_PAREN, 0)!;
Expand Down
60 changes: 60 additions & 0 deletions test/parser/spark/contextCollect/entityCollector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,66 @@ describe('SparkSQL entity collector tests', () => {
});
});

test('should not collect lateral view generator function as view entity', () => {
const spark = new SparkSQL();
const sql = `SELECT * FROM person LATERAL VIEW EXPLODE(ARRAY(30, 60)) tableName AS c_age;`;
const allEntities = spark.getAllEntities(sql);

expect(
allEntities.map((e) => ({
type: e.entityContextType,
text: e.text,
}))
).toEqual([
{ type: EntityContextType.TABLE, text: 'person' },
{ type: EntityContextType.QUERY_RESULT, text: '*' },
]);
});

test('should collect entities accurately with chained, qualified, and subquery lateral views', () => {
const spark = new SparkSQL();
const sql = `
CREATE VIEW db1.v_person AS
SELECT p.name, c_age, d_val
FROM db1.person p
JOIN db2.dept d ON p.dept_id = d.id
LATERAL VIEW db_func.explode_func(p.ages) t1 AS c_age
LATERAL VIEW OUTER catalog1.db_func.posexplode_func((SELECT collect_list(score) FROM db3.scores)) t2 AS d_pos, d_val
LATERAL VIEW IDENTIFIER('explode')(ARRAY(1, 2)) t3 AS e_val;
`;
const allEntities = spark.getAllEntities(sql);

expect(
allEntities.map((e) => ({
type: e.entityContextType,
text: e.text,
}))
).toEqual([
{ type: EntityContextType.TABLE, text: 'db3.scores' },
{ type: EntityContextType.QUERY_RESULT, text: 'collect_list(score)' },
{ type: EntityContextType.TABLE, text: 'db1.person' },
{ type: EntityContextType.TABLE, text: 'db2.dept' },
{ type: EntityContextType.QUERY_RESULT, text: 'p.name, c_age, d_val' },
{ type: EntityContextType.VIEW_CREATE, text: 'db1.v_person' },
]);
});

test('should not collect lateral view generator function in from-select statement', () => {
const spark = new SparkSQL();
const sql = `FROM person SELECT * LATERAL VIEW OUTER db.explode_fn(ARRAY(30, 60)) tableName AS c_age;`;
const allEntities = spark.getAllEntities(sql);

expect(
allEntities.map((e) => ({
type: e.entityContextType,
text: e.text,
}))
).toEqual([
{ type: EntityContextType.TABLE, text: 'person' },
{ type: EntityContextType.QUERY_RESULT, text: '*' },
]);
});

test('insert into table values', () => {
const testingContext = splitListener.statementsContext[5];

Expand Down
26 changes: 26 additions & 0 deletions test/parser/spark/errorListener.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const sql1 = `ALTER VIEW`;
const sql2 = `SELECT * FROM `;
const sql3 = `DROP SCHEMA aaa aaa`;
const sql4 = `SELECT name, age FROM person ORDER BY length( `;
const sql5 = `SELECT * FROM person LATERAL VIEW `;
const sql6 = `SELECT * FROM person LATERAL VIEW OUTER `;

describe('SparkSQL validate invalid sql and test msg', () => {
const spark = new SparkSQL();
Expand Down Expand Up @@ -47,6 +49,20 @@ describe('SparkSQL validate invalid sql and test msg', () => {
);
});

test('validate unComplete lateral view sql', () => {
const errors = spark.validate(sql5);
expect(errors.length).toBe(1);
expect(errors[0].message).toBe(
`Statement is incomplete, expecting an existing function or a keyword`
);

const outerErrors = spark.validate(sql6);
expect(outerErrors.length).toBe(1);
expect(outerErrors[0].message).toBe(
`Statement is incomplete, expecting an existing function or a keyword`
);
});

test('validate random text cn', () => {
spark.locale = 'zh_CN';
const errors = spark.validate(randomText);
Expand Down Expand Up @@ -81,6 +97,16 @@ describe('SparkSQL validate invalid sql and test msg', () => {
`语句不完整,期望一个存在的function或者一个存在的column或者一个关键字`
);
});

test('validate unComplete lateral view sql cn', () => {
const errors = spark.validate(sql5);
expect(errors.length).toBe(1);
expect(errors[0].message).toBe(`语句不完整,期望一个存在的function或者一个关键字`);

const outerErrors = spark.validate(sql6);
expect(outerErrors.length).toBe(1);
expect(outerErrors[0].message).toBe(`语句不完整,期望一个存在的function或者一个关键字`);
});
});

describe('SparkSQL validate multiple erroneous statements', () => {
Expand Down
68 changes: 68 additions & 0 deletions test/parser/spark/suggestion/syntaxSuggestion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -842,4 +842,72 @@ describe('Spark SQL Syntax Suggestion', () => {
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']);
});

test('Lateral view function suggestion', () => {
const cases: { sql: string; column: number; expected: string[]; checkOuterKw?: boolean }[] =
[
{
sql: `SELECT * FROM person LATERAL VIEW `,
column: 35,
expected: [],
checkOuterKw: true,
},
{
sql: `SELECT * FROM person LATERAL VIEW exp`,
column: 38,
expected: ['exp'],
},
{
sql: `SELECT * FROM person LATERAL VIEW db.`,
column: 38,
expected: ['db', '.'],
},
{
sql: `SELECT * FROM person LATERAL VIEW db.exp`,
column: 41,
expected: ['db', '.', 'exp'],
},
{
sql: `SELECT * FROM person LATERAL VIEW cat.db.exp`,
column: 45,
expected: ['cat', '.', 'db', '.', 'exp'],
},
{
sql: `SELECT * FROM person LATERAL VIEW OUTER db.exp`,
column: 47,
expected: ['db', '.', 'exp'],
},
{
sql: `SELECT * FROM person LATERAL VIEW EXPLODE(arr1) t1 AS c1 LATERAL VIEW pos`,
column: 74,
expected: ['pos'],
},
{
sql: `FROM person SELECT * LATERAL VIEW exp`,
column: 38,
expected: ['exp'],
},
];

cases.forEach(({ sql, column, expected, checkOuterKw }) => {
const result = spark.getSuggestionAtCaretPosition(sql, {
lineNumber: 1,
column,
});
const syntaxes = result?.syntax;
const fnSuggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === EntityContextType.FUNCTION
);
const viewSuggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === EntityContextType.VIEW
);

expect(fnSuggestion).not.toBeUndefined();
expect(viewSuggestion).toBeUndefined();
expect(fnSuggestion?.wordRanges.map((token) => token.text)).toEqual(expected);
if (checkOuterKw) {
expect(result?.keywords).toContain('OUTER');
}
});
});
});
18 changes: 18 additions & 0 deletions test/parser/spark/syntax/fixtures/selectLateralView.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,21 @@ GROUP BY c_age;
SELECT * FROM person LATERAL VIEW EXPLODE(ARRAY()) tableName AS c_age;

SELECT * FROM person LATERAL VIEW OUTER EXPLODE(ARRAY()) tableName AS c_age;

SELECT * FROM person LATERAL VIEW schema1.custom_explode(ARRAY(30, 60)) tableName AS c_age;

SELECT * FROM person LATERAL VIEW catalog1.schema1.custom_explode(ARRAY(30, 60)) tableName AS c_age;

SELECT * FROM person LATERAL VIEW IDENTIFIER('explode')(ARRAY(30, 60)) tableName AS c_age;

SELECT * FROM person LATERAL VIEW POSEXPLODE(ARRAY(30, 60)) tableName AS pos, c_age;

SELECT * FROM person LATERAL VIEW OUTER POSEXPLODE(ARRAY(30, 60)) tableName pos, c_age;

SELECT * FROM person LATERAL VIEW EXPLODE(ARRAY(30, 60)) tableName;

SELECT * FROM person LATERAL VIEW EXPLODE(ARRAY(30, 60));

FROM person SELECT * LATERAL VIEW EXPLODE(ARRAY(30, 60)) tableName AS c_age;

SELECT c_age LATERAL VIEW EXPLODE(ARRAY(30, 60)) tableName AS c_age;