Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(knex): nested queries result in wrong span names #1537

Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class KnexInstrumentation extends InstrumentationBase<any> {
return function wrapped_logging_method(this: any, query: any) {
const config = this.client.config;

const table = this.builder?._single?.table;
const table = utils.extractTableName(this.builder);
// `method` actually refers to the knex API method - Not exactly "operation"
// in the spec sense, but matches most of the time.
const operation = query?.method;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,11 @@ export const limitLength = (str: string, maxLength: number) => {
}
return str;
};

export const extractTableName = (builder: any): string => {
nachogiljaldo marked this conversation as resolved.
Show resolved Hide resolved
const table = builder?._single?.table;
if (typeof table === 'object') {
return extractTableName(table);
}
return table;
};
269 changes: 269 additions & 0 deletions plugins/node/opentelemetry-instrumentation-knex/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,275 @@ describe('Knex instrumentation', () => {
}
);
});

describe('nested queries', () => {
it('should correctly identify the table in nested queries', async () => {
const parentSpan = tracer.startSpan('parentSpan');
await context.with(
trace.setSpan(context.active(), parentSpan),
async () => {
await client.schema.createTable('testTable1', (table: any) => {
table.string('title');
});
await client.insert({ title: 'test1' }).into('testTable1');

const builder = client('testTable1').select('*');
const clone = builder.clone().clear('order');

const nestedQueryBuilder = builder.client
.queryBuilder()
.count('* AS count')
.from(clone.as('inner'))
.first();

const total = await nestedQueryBuilder;
assert.deepEqual(total, { count: 1 });

parentSpan.end();

const instrumentationSpans = memoryExporter.getFinishedSpans();
const last = instrumentationSpans.pop() as any;
assertSpans(instrumentationSpans, [
{
statement: 'create table `testTable1` (`title` varchar(255))',
parentSpan,
},
{
op: 'insert',
table: 'testTable1',
statement: 'insert into `testTable1` (`title`) values (?)',
parentSpan,
},
{
op: 'first',
table: 'testTable1',
statement:
'select count(*) as `count` from (select * from `te..',
parentSpan,
},
]);
assert.strictEqual(instrumentationSpans[0].name, ':memory:');
assert.strictEqual(
instrumentationSpans[1].name,
'insert :memory:.testTable1'
);

assert(last.name, 'parentSpan');
}
);
});

it('should correctly identify the table in double nested queries', async () => {
const parentSpan = tracer.startSpan('parentSpan');
await context.with(
trace.setSpan(context.active(), parentSpan),
async () => {
await client.schema.createTable('testTable1', (table: any) => {
table.string('title');
});
await client.insert({ title: 'test1' }).into('testTable1');

const builder = client('testTable1').select('*');
const clone = builder.clone().clear('order');

const nestedQueryBuilder = builder.client
.queryBuilder()
.count('* AS count')
.from(clone.as('inner'))
.first();

const nestedClone = nestedQueryBuilder.clone().clear('order');
const totalDoubleNested = await nestedQueryBuilder.client
.queryBuilder()
.count('* AS count2')
.from(nestedClone.as('inner2'))
.first();
assert.deepEqual(totalDoubleNested, { count2: 1 });

parentSpan.end();

const instrumentationSpans = memoryExporter.getFinishedSpans();
const last = instrumentationSpans.pop() as any;
assertSpans(instrumentationSpans, [
{
statement: 'create table `testTable1` (`title` varchar(255))',
parentSpan,
},
{
op: 'insert',
table: 'testTable1',
statement: 'insert into `testTable1` (`title`) values (?)',
parentSpan,
},
{
op: 'first',
table: 'testTable1',
statement:
'select count(*) as `count2` from (select count(*) ..',
parentSpan,
},
]);
assert.strictEqual(instrumentationSpans[0].name, ':memory:');
assert.strictEqual(
instrumentationSpans[1].name,
'insert :memory:.testTable1'
);

assert(last.name, 'parentSpan');
}
);
});

it('should correctly identify the table in join with nested table', async () => {
const parentSpan = tracer.startSpan('parentSpan');
await context.with(
trace.setSpan(context.active(), parentSpan),
async () => {
await client.schema.createTable('testTable1', (table: any) => {
table.string('title');
});
await client.insert({ title: 'test1' }).into('testTable1');

await client.schema.createTable('testTable2', (table: any) => {
table.string('title');
});
await client.insert({ title: 'test2' }).into('testTable2');

const builder = client('testTable1').select('*');
const clone = builder.clone().clear('order');

const nestedQueryBuilder = builder.client
.queryBuilder()
.count('* AS count')
.from(clone.as('inner'))
.first();

const totalDoubleNested = await nestedQueryBuilder.client
.queryBuilder()
.from('testTable2')
.leftJoin(nestedQueryBuilder.as('nested_query'))
.first();
assert.deepEqual(totalDoubleNested, { title: 'test2', count: 1 });

parentSpan.end();

const instrumentationSpans = memoryExporter.getFinishedSpans();
const last = instrumentationSpans.pop() as any;
assertSpans(instrumentationSpans, [
{
statement: 'create table `testTable1` (`title` varchar(255))',
parentSpan,
},
{
op: 'insert',
table: 'testTable1',
statement: 'insert into `testTable1` (`title`) values (?)',
parentSpan,
},
{
statement: 'create table `testTable2` (`title` varchar(255))',
parentSpan,
},
{
op: 'insert',
table: 'testTable2',
statement: 'insert into `testTable2` (`title`) values (?)',
parentSpan,
},
{
op: 'first',
table: 'testTable2',
statement:
'select * from `testTable2` left join (select count..',
parentSpan,
},
]);
assert.strictEqual(instrumentationSpans[0].name, ':memory:');
assert.strictEqual(
instrumentationSpans[1].name,
'insert :memory:.testTable1'
);

assert(last.name, 'parentSpan');
}
);
});

it('should correctly identify the table in join nested table with table', async () => {
const parentSpan = tracer.startSpan('parentSpan');
await context.with(
trace.setSpan(context.active(), parentSpan),
async () => {
await client.schema.createTable('testTable1', (table: any) => {
table.string('title');
});
await client.insert({ title: 'test1' }).into('testTable1');

await client.schema.createTable('testTable2', (table: any) => {
table.string('title');
});
await client.insert({ title: 'test2' }).into('testTable2');

const builder = client('testTable1').select('*');
const clone = builder.clone().clear('order');

const nestedQueryBuilder = builder.client
.queryBuilder()
.count('* AS count')
.from(clone.as('inner'))
.first();

const totalDoubleNested = await nestedQueryBuilder.client
.queryBuilder()
.from(nestedQueryBuilder.as('nested_query'))
.leftJoin('testTable2')
.first();
assert.deepEqual(totalDoubleNested, { title: 'test2', count: 1 });

parentSpan.end();

const instrumentationSpans = memoryExporter.getFinishedSpans();
const last = instrumentationSpans.pop() as any;
assertSpans(instrumentationSpans, [
{
statement: 'create table `testTable1` (`title` varchar(255))',
parentSpan,
},
{
op: 'insert',
table: 'testTable1',
statement: 'insert into `testTable1` (`title`) values (?)',
parentSpan,
},
{
statement: 'create table `testTable2` (`title` varchar(255))',
parentSpan,
},
{
op: 'insert',
table: 'testTable2',
statement: 'insert into `testTable2` (`title`) values (?)',
parentSpan,
},
{
op: 'first',
table: 'testTable1',
statement:
'select * from (select count(*) as `count` from (se..',
parentSpan,
},
]);
assert.strictEqual(instrumentationSpans[0].name, ':memory:');
assert.strictEqual(
instrumentationSpans[1].name,
'insert :memory:.testTable1'
);

assert(last.name, 'parentSpan');
nachogiljaldo marked this conversation as resolved.
Show resolved Hide resolved
}
);
});
});
});

describe('Disabling instrumentation', () => {
Expand Down