Skip to content

Commit

Permalink
feat: simplify tables compare (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomer-friedman authored Jun 19, 2023
1 parent 31073dd commit b765cc9
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 56 deletions.
36 changes: 1 addition & 35 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions packages/expect-opentelemetry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@
"@opentelemetry/semantic-conventions": "^1.10.1",
"@traceloop/otel-proto": "^0.7.0",
"axios": "^1.3.4",
"deep-equal": "^2.2.0",
"node-sql-parser": "^4.7.0"
"deep-equal": "^2.2.0"
},
"devDependencies": {
"@types/deep-equal": "^1.0.1",
Expand Down
24 changes: 20 additions & 4 deletions packages/expect-opentelemetry/src/matchers/utils/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,30 @@ import { opentelemetry } from '@traceloop/otel-proto';
import { objectCompare, stringCompare } from './comparators';
import { CompareOptions } from './compare-types';

export const extractAttributeStringValues = (
spans: opentelemetry.proto.trace.v1.ISpan[],
attribute: string,
) => {
return spans
.map((span) => {
return span.attributes?.find(
(attribute: opentelemetry.proto.common.v1.IKeyValue) =>
attribute.key === attribute,
)?.value?.stringValue;
})
.filter((statement) => !!statement) as string[];
};

export const filterByAttributeKey = (
spans: opentelemetry.proto.trace.v1.ISpan[],
attName: string,
) =>
spans.filter((span) => {
return span.attributes?.find((attribute) => {
return attribute.key === attName;
});
return span.attributes?.find(
(attribute: opentelemetry.proto.common.v1.IKeyValue) => {
return attribute.key === attName;
},
);
});

export const filterBySpanKind = (
Expand Down Expand Up @@ -38,7 +54,7 @@ export const filterByAttributeIntValue = (
) =>
spans.filter((span) => {
return span.attributes?.find(
(attribute) =>
(attribute: opentelemetry.proto.common.v1.IKeyValue) =>
attribute.key === attName &&
attribute.value?.intValue.toNumber() === attValue,
);
Expand Down
51 changes: 36 additions & 15 deletions packages/expect-opentelemetry/src/resources/postgresql-query.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
import { opentelemetry } from '@traceloop/otel-proto';
import { Parser } from 'node-sql-parser';
import {
CompareOptions,
extractAttributeStringValues,
filterByAttributeStringValue,
} from '../matchers/utils';

const tablesRegex = /(from|join|into|update|alter)\s+(?<table>\S+)/gi;

export class PostgreSQLQuery {
constructor(
readonly spans: opentelemetry.proto.trace.v1.ISpan[],
private readonly serviceName: string,
private parser = new Parser(),
) {}

withDatabaseName(name: string | RegExp, options?: CompareOptions) {
Expand Down Expand Up @@ -40,7 +41,12 @@ export class PostgreSQLQuery {

if (filteredSpans.length === 0) {
throw new Error(
`No query by ${this.serviceName} to postgresql with statement ${statement} was found`,
`No query by ${
this.serviceName
} to postgresql with statement ${statement} was found. Found statements: ${extractAttributeStringValues(
this.spans,
SemanticAttributes.DB_STATEMENT,
)}`,
);
}

Expand All @@ -50,7 +56,8 @@ export class PostgreSQLQuery {
withOperations(...operations: string[]) {
const filteredSpans = this.spans.filter((span) => {
const statement = span.attributes?.find(
(attribute) => attribute.key === SemanticAttributes.DB_STATEMENT,
(attribute: opentelemetry.proto.common.v1.IKeyValue) =>
attribute.key === SemanticAttributes.DB_STATEMENT,
)?.value?.stringValue;

if (!statement) {
Expand All @@ -66,7 +73,12 @@ export class PostgreSQLQuery {

if (filteredSpans.length === 0) {
throw new Error(
`No query by ${this.serviceName} to postgresql with operations ${operations} was found`,
`No query by ${
this.serviceName
} to postgresql with operations ${operations} was found. Found statements: ${extractAttributeStringValues(
this.spans,
SemanticAttributes.DB_STATEMENT,
)}`,
);
}

Expand All @@ -76,32 +88,41 @@ export class PostgreSQLQuery {
withTables(...tables: string[]) {
const filteredSpans = this.spans.filter((span) => {
const statement = span.attributes?.find(
(attribute) => attribute.key === SemanticAttributes.DB_STATEMENT,
(attribute: opentelemetry.proto.common.v1.IKeyValue) =>
attribute.key === SemanticAttributes.DB_STATEMENT,
)?.value?.stringValue;

if (!statement) {
return false;
}

const allTablesInStatement = this.parser
.tableList(prepareQuery(statement), { database: 'PostgresQL' })
.map((table) => table.split('::')[2].trim());
const matches = statement.match(tablesRegex);
const cleaned = matches?.map((elem: string) => {
const [_, second] = elem.split(' ');
return second
.replace('"', '')
.replace('(', '')
.replace(')', '')
.replace('\n', '')
.toLocaleLowerCase();
});

return tables.every((table) =>
allTablesInStatement.includes(table.toLowerCase()),
cleaned?.includes(table.toLocaleLowerCase()),
);
});

if (filteredSpans.length === 0) {
throw new Error(
`No query by ${this.serviceName} to postgresql with tables ${tables} was found`,
`No query by ${
this.serviceName
} to postgresql with tables ${tables} was found. Found statements: ${extractAttributeStringValues(
this.spans,
SemanticAttributes.DB_STATEMENT,
)}`,
);
}

return new PostgreSQLQuery(filteredSpans, this.serviceName);
}
}

const prepareQuery = (
query: string, // remove double quotes and replace %s with 111
) => query.replace(/"/g, '').replace(/%s/g, '111').toLocaleLowerCase();

0 comments on commit b765cc9

Please sign in to comment.