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
7 changes: 6 additions & 1 deletion src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1112,8 +1112,13 @@ export function defaultParamTypesFor(dialect: Dialect): ParamTypes {
numbered: ['$'],
};
case 'mssql':
return {
named: ['@', ':'],
};
case 'oracle':
return {
named: [':'],
numbered: [':'],
};
case 'bigquery':
return {
Expand All @@ -1125,7 +1130,7 @@ export function defaultParamTypesFor(dialect: Dialect): ParamTypes {
return {
positional: true,
numbered: ['?'],
named: [':', '@'],
named: [':', '@', '$'],
};
default:
return {
Expand Down
94 changes: 87 additions & 7 deletions test/identifier/single-statement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ describe('identifier', () => {
text: query,
type: 'CREATE_FUNCTION',
executionType: 'MODIFICATION',
parameters: [],
parameters: ['@DATE', '@ISOweek'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a bug to me, since they're not parameters that the user would need to fill in. We can at least detect variables within a function/body by checking if the preceding token is DECLARE, though I'm a bit of a loss for dealing with function parameters.

tables: [],
},
];
Expand Down Expand Up @@ -1445,18 +1445,18 @@ describe('identifier', () => {
});

it('Should extract named Parameters', () => {
const actual = identify('SELECT * FROM Persons where x = :one and y = :two and a = :one', {
const actual = identify('SELECT * FROM Persons where x = @one and y = @two and a = @one', {
dialect: 'mssql',
strict: true,
});
const expected = [
{
start: 0,
end: 61,
text: 'SELECT * FROM Persons where x = :one and y = :two and a = :one',
text: 'SELECT * FROM Persons where x = @one and y = @two and a = @one',
type: 'SELECT',
executionType: 'LISTING',
parameters: [':one', ':two'],
parameters: ['@one', '@two'],
tables: [],
},
];
Expand All @@ -1465,18 +1465,98 @@ describe('identifier', () => {
});

it('Should extract named Parameters with trailing commas', () => {
const actual = identify('SELECT * FROM Persons where x in (:one, :two, :three)', {
const actual = identify('SELECT * FROM Persons where x in (@one, @two, @three)', {
dialect: 'mssql',
strict: true,
});
const expected = [
{
start: 0,
end: 52,
text: 'SELECT * FROM Persons where x in (:one, :two, :three)',
text: 'SELECT * FROM Persons where x in (@one, @two, @three)',
type: 'SELECT',
executionType: 'LISTING',
parameters: ['@one', '@two', '@three'],
tables: [],
},
];

expect(actual).to.eql(expected);
});

it('Should extract mssql colon-prefixed named parameters', () => {
const actual = identify('SELECT * FROM Persons where x = :one and y = :two and a = :one', {
dialect: 'mssql',
strict: true,
});
const expected = [
{
start: 0,
end: 61,
text: 'SELECT * FROM Persons where x = :one and y = :two and a = :one',
type: 'SELECT',
executionType: 'LISTING',
parameters: [':one', ':two'],
tables: [],
},
];

expect(actual).to.eql(expected);
});

it('Should extract oracle named parameters', () => {
const actual = identify('SELECT * FROM persons WHERE id = :one AND status = :two', {
dialect: 'oracle',
strict: true,
});
const expected = [
{
start: 0,
end: 54,
text: 'SELECT * FROM persons WHERE id = :one AND status = :two',
type: 'SELECT',
executionType: 'LISTING',
parameters: [':one', ':two'],
tables: [],
},
];

expect(actual).to.eql(expected);
});

it('Should extract oracle numbered parameters', () => {
const actual = identify('SELECT * FROM persons WHERE id = :1 AND status = :2', {
dialect: 'oracle',
strict: true,
});
const expected = [
{
start: 0,
end: 50,
text: 'SELECT * FROM persons WHERE id = :1 AND status = :2',
type: 'SELECT',
executionType: 'LISTING',
parameters: [':1', ':2'],
tables: [],
},
];

expect(actual).to.eql(expected);
});

it('Should extract sqlite $name parameters', () => {
const actual = identify('SELECT * FROM persons WHERE id = $myid', {
dialect: 'sqlite',
strict: true,
});
const expected = [
{
start: 0,
end: 37,
text: 'SELECT * FROM persons WHERE id = $myid',
type: 'SELECT',
executionType: 'LISTING',
parameters: [':one', ':two', ':three'],
parameters: ['$myid'],
tables: [],
},
];
Expand Down
3 changes: 2 additions & 1 deletion test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ describe('Regression tests', () => {
{ strict: false, dialect: 'mssql' as Dialect },
);
result.forEach((res) => {
expect(res.parameters.length).to.equal(0);
// :: cast syntax should not produce colon-prefixed parameters
expect(res.parameters.every((param) => !param.startsWith(':'))).to.equal(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like can rework the this test to then only do identify('SET @g = geometry::STGeomFromText('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))', 0);') if just checking that cast isn't parsed to a parameter.

However, similar to above, would be good to think about how we should handle these parameters here.

});
});

Expand Down
14 changes: 7 additions & 7 deletions test/parser/single-statements.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@ describe('parser', () => {

it('should extract mssql parameters', () => {
const actual = parse(
'select x from a where x = :foo',
'select x from a where x = @foo',
true,
'mssql',
false,
Expand All @@ -826,13 +826,13 @@ describe('parser', () => {
},
{
type: 'parameter',
value: ':foo',
value: '@foo',
start: 26,
end: 29,
},
];
expect(actual.tokens).to.eql(expected);
expect(actual.body[0].parameters).to.eql([':foo']);
expect(actual.body[0].parameters).to.eql(['@foo']);
});

it('should not identify params in a comment', () => {
Expand Down Expand Up @@ -875,7 +875,7 @@ describe('parser', () => {

it('should extract multiple mssql parameters', () => {
const actual = parse(
'select x from a where x = :foo and y = :bar',
'select x from a where x = @foo and y = @bar',
true,
'mssql',
false,
Expand All @@ -897,7 +897,7 @@ describe('parser', () => {
},
{
type: 'parameter',
value: ':foo',
value: '@foo',
start: 26,
end: 29,
},
Expand All @@ -909,13 +909,13 @@ describe('parser', () => {
},
{
type: 'parameter',
value: ':bar',
value: '@bar',
start: 39,
end: 42,
},
];
expect(actual.tokens).to.eql(expected);
expect(actual.body[0].parameters).to.eql([':foo', ':bar']);
expect(actual.body[0].parameters).to.eql(['@foo', '@bar']);
});
});
});
Expand Down
52 changes: 47 additions & 5 deletions test/tokenizer/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ describe('scan', () => {
['?', 'generic'],
['?', 'mysql'],
['?', 'sqlite'],
[':', 'mssql'],
['@', 'mssql'],
].forEach(([ch, dialect]) => {
it(`scans just ${ch} as parameter for ${dialect}`, () => {
const input = `${ch}`;
Expand Down Expand Up @@ -322,10 +322,7 @@ describe('scan', () => {
expect(actual).to.eql(expected);
});
});
[
['$', 'psql'],
[':', 'mssql'],
].forEach(([ch, dialect]) => {
[['$', 'psql']].forEach(([ch, dialect]) => {
it(`should scan ${ch}1 for ${dialect}`, () => {
const input = `${ch}1`;
const actual = scanToken(
Expand Down Expand Up @@ -369,6 +366,24 @@ describe('scan', () => {
it('should not include trailing non-alphanumerics for mssql', () => {
const paramTypes = defaultParamTypesFor('mssql');
[
{
actual: scanToken(initState('@one,'), 'mssql', paramTypes),
expected: {
type: 'parameter',
value: '@one',
start: 0,
end: 3,
},
},
{
actual: scanToken(initState('@two)'), 'mssql', paramTypes),
expected: {
type: 'parameter',
value: '@two',
start: 0,
end: 3,
},
},
{
actual: scanToken(initState(':one,'), 'mssql', paramTypes),
expected: {
Expand All @@ -390,6 +405,33 @@ describe('scan', () => {
].forEach(({ actual, expected }) => expect(actual).to.eql(expected));
});

it('should not include trailing non-alphanumerics for oracle', () => {
const paramTypes = defaultParamTypesFor('oracle');
[
{
actual: scanToken(initState(':one,'), 'oracle', paramTypes),
expected: {
type: 'parameter',
value: ':one',
start: 0,
end: 3,
},
},
].forEach(({ actual, expected }) => expect(actual).to.eql(expected));
});

it('should recognize $name for sqlite', () => {
const paramTypes = defaultParamTypesFor('sqlite');
const actual = scanToken(initState('$myvar'), 'sqlite', paramTypes);
const expected = {
type: 'parameter',
value: '$myvar',
start: 0,
end: 5,
};
expect(actual).to.eql(expected);
});

describe('custom parameters', () => {
describe('positional parameters', () => {
const paramTypes = {
Expand Down