Skip to content

Commit

Permalink
SNOW-855088: Returnig result rows as arrays.
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-pmotacki committed Jul 28, 2023
1 parent 753e9c0 commit 2e1b2a9
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 34 deletions.
33 changes: 32 additions & 1 deletion lib/connection/result/result.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var Chunk = require('./chunk');
var ResultStream = require('./result_stream');
var ChunkCache = require('./chunk_cache');
var Column = require('./column');
var RowMode = require('./../../constants/row_mode');
var Parameters = require('../../parameters');
var StatementType = require('./statement_type');

Expand Down Expand Up @@ -96,7 +97,37 @@ function Result(options)
// indices of the columns with the corresponding names
this._mapColumnNameToIndices = mapColumnNameToIndices = {};

for (index = 0; index < numColumns; index++) {
const rowMode = options.rowMode;
const resultContainsDuplicatedColumns = (rowtype) => {
const columnNames = rowtype.map(rt => rt.name);
return columnNames.length !== new Set(columnNames).size
}

//Prepare renamed columns for duplicates if row mode was set to 'object_with_indexed_column_names'
if (rowMode && rowMode === RowMode.OBJECT_WITH_INDEXED_COLUMN_NAMES && resultContainsDuplicatedColumns(rowtype)){

const columnNames = new Set(rowtype.map(el => el.name));
const quntityOfColumnNames = new Map();

for (let index = 0; index < numColumns; index++) {
let columnName = rowtype[index].name;
if (quntityOfColumnNames.has(columnName)) {
let times = quntityOfColumnNames.get(columnName) + 1;
let newColumnName = columnName + '_' + times;
while (columnNames.has(newColumnName)) {
times += 1;
newColumnName = columnName + '_' + times
}
quntityOfColumnNames.set(columnName, times);
rowtype[index].name = newColumnName;
columnNames.add(newColumnName)
} else {
quntityOfColumnNames.set(columnName, 1);
}
}
}

for (let index = 0; index < numColumns; index++) {

// create a new column and add it to the columns array
columns[index] = column =
Expand Down
13 changes: 5 additions & 8 deletions lib/connection/result/row_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,25 +434,22 @@ function buildMapColumnExtractFnNames(columns, fetchAsString)
* @param {Object} row
* @param {Object[]} columns
* @param {Object} [mapColumnIdToExtractFnName]
* @param {String?} rowMode - string value ('array' or 'object'). Default is 'object' when parameter isn't set.
* @param {String?} rowMode - string value ('array', 'object' or 'object_with_indexed_column_names').
* Default is 'object' when parameter isn't set.
*
* @returns {Object}
*/
function externalizeRow(row, columns, mapColumnIdToExtractFnName, rowMode, duplicatedColumnNames)
function externalizeRow(row, columns, mapColumnIdToExtractFnName, rowMode)
{
const isArrayRowMode = rowMode === RowMode.ARRAY;

var externalizedRow = isArrayRowMode ? [] : {};

function columnNameWithSufixForDuplicated(column, duplicatedNames) {
return duplicatedNames.includes(column.getName()) ? column.getName() + '_' + column.getIndex() : column.getName();
}
let externalizedRow = isArrayRowMode ? [] : {};

for (var index = 0, length = columns.length; index < length; index++)
{
var column = columns[index];
var extractFnName = mapColumnIdToExtractFnName[column.getId()];
externalizedRow[isArrayRowMode ? index : columnNameWithSufixForDuplicated(column, duplicatedColumnNames)] = row[extractFnName](column.getId());
externalizedRow[isArrayRowMode ? index : column.getName()] = row[extractFnName](column.getId());
}

return externalizedRow;
Expand Down
10 changes: 6 additions & 4 deletions lib/connection/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ exports.createStatementPostExec = function (
const rowMode = statementOptions.rowMode;
if (Util.exists(rowMode)) {
Errors.checkArgumentValid(RowMode.isValidRowMode(rowMode),

Check warning on line 194 in lib/connection/statement.js

View check run for this annotation

Codecov / codecov/patch

lib/connection/statement.js#L194

Added line #L194 was not covered by tests
ErrorCodes.ERR_STMT_STREAM_ROWS_INVALID_ROW_MODE);
ErrorCodes.ERR_STMT_STREAM_ROWS_INVALID_ROW_MODE, JSON.stringify(rowMode));
}

// validate non-user-specified arguments
Expand Down Expand Up @@ -365,7 +365,7 @@ function createContextPreExec(
const rowMode = statementOptions.rowMode;
if (Util.exists(rowMode)) {
Errors.checkArgumentValid(RowMode.isValidRowMode(rowMode),
ErrorCodes.ERR_STMT_STREAM_ROWS_INVALID_ROW_MODE);
ErrorCodes.ERR_STMT_STREAM_ROWS_INVALID_ROW_MODE, JSON.stringify(rowMode));
}

// create a statement context
Expand Down Expand Up @@ -451,6 +451,7 @@ function BaseStatement(
context.services = services;
context.connectionConfig = connectionConfig;
context.isFetchingResult = true;
context.rowMode = statementOptions.rowMode || connectionConfig.getRowMode();

// TODO: add the parameters map to the statement context

Expand Down Expand Up @@ -793,7 +794,8 @@ function createOnStatementRequestSuccRow(statement, context)
response: body,
statement: statement,
services: context.services,
connectionConfig: context.connectionConfig
connectionConfig: context.connectionConfig,
rowMode: context.rowMode
});
// save the statement id
context.statementId = context.result.getStatementId();
Expand Down Expand Up @@ -1063,7 +1065,7 @@ function createFnFetchRows(statement, context)
// check for invalid rowMode
if (Util.exists(rowMode)) {
Errors.checkArgumentValid(RowMode.isValidRowMode(rowMode),
ErrorCodes.ERR_STMT_STREAM_ROWS_INVALID_ROW_MODE);
ErrorCodes.ERR_STMT_STREAM_ROWS_INVALID_ROW_MODE, JSON.stringify(rowMode));
}

// if we're still trying to fetch the result, create an error of our own
Expand Down
4 changes: 3 additions & 1 deletion lib/constants/row_mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
*/
const ARRAY = 'array';
const OBJECT = 'object';
const isValidRowMode = (rowMode) => [ARRAY, OBJECT].includes(rowMode)
const OBJECT_WITH_INDEXED_COLUMN_NAMES = 'object_with_indexed_column_names';
const isValidRowMode = (rowMode) => [ARRAY, OBJECT, OBJECT_WITH_INDEXED_COLUMN_NAMES].includes(rowMode)

exports.ARRAY = ARRAY;
exports.OBJECT = OBJECT;
exports.OBJECT_WITH_INDEXED_COLUMN_NAMES = OBJECT_WITH_INDEXED_COLUMN_NAMES;
exports.isValidRowMode = isValidRowMode;
127 changes: 107 additions & 20 deletions test/integration/testRowMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,94 @@ const RowMode = require('./../../lib/constants/row_mode');


describe('Test row mode', function () {
this.timeout(5000);
this.timeout(150000);
let connection;
const sql = `select *
from (select 'a' as key, 1 as foo, 3 as name) as table1
join (select 'a' as key, 2 as foo, 3 as name2) as table2 on table1.key = table2.key
join (select 'a' as key, 3 as foo) as table3 on table1.key = table3.key`;

const rowModeOptions = [undefined, RowMode.OBJECT, RowMode.ARRAY];
const expectedArray = ['a', 1, 3, 'a', 2, 3, 'a', 3];
const expectedObject = {KEY_0: 'a', FOO_1: 1, NAME: 3, KEY_3: 'a', FOO_4: 2, NAME2: 3, KEY_6: 'a', FOO_7: 3};
const expectedObject = {KEY: 'a', FOO: 3, NAME: 3, NAME2: 3};
const expectedObjectWithIndexedColumnNames = {KEY: 'a', FOO: 1, NAME: 3, KEY_2: 'a', FOO_2: 2, NAME2: 3, KEY_3: 'a', FOO_3: 3};

function expectedResult(connectionRowMode, statementRowMode) {
if (statementRowMode) {
return statementRowMode === RowMode.ARRAY ? expectedArray : expectedObject;
} else if (connectionRowMode) {
return connectionRowMode === RowMode.ARRAY ? expectedArray : expectedObject;
} else {
return expectedObject;
const testCases = [
{
connectionRowMode: RowMode.OBJECT,
statementRowModes: [
{
type: RowMode.ARRAY,
expected: expectedArray
},
{
type: RowMode.OBJECT_WITH_INDEXED_COLUMN_NAMES,
expected: expectedObjectWithIndexedColumnNames
},
{
type: undefined,
expected: expectedObject
}
]
},
{
connectionRowMode: RowMode.ARRAY,
statementRowModes: [
{
type: undefined,
expected: expectedArray
},
{
type: RowMode.OBJECT_WITH_INDEXED_COLUMN_NAMES,
expected: expectedObjectWithIndexedColumnNames
},
{
type: RowMode.OBJECT,
expected: expectedObject
},
]
},
{
connectionRowMode: RowMode.OBJECT_WITH_INDEXED_COLUMN_NAMES,
statementRowModes: [
{
type: undefined,
expected: expectedObjectWithIndexedColumnNames
},
{
type: RowMode.ARRAY,
expected: expectedArray
},
{
type: RowMode.OBJECT,
expected: expectedObject
},
]
}
}
];


rowModeOptions.forEach(connectionRowMode => {
describe(`rowMode ${connectionRowMode} in connection`, function () {
testCases.forEach(testCase => {
describe(`rowMode ${testCase.connectionRowMode} in connection`, function () {
before(function (done) {
connection = testUtil.createConnection({rowMode: connectionRowMode});
connection = testUtil.createConnection({rowMode: testCase.connectionRowMode});
testUtil.connect(connection, done);
});
after(function (done) {
testUtil.destroyConnection(connection, done);
});

rowModeOptions.forEach(statementRowMode => {
describe(`rowMode ${statementRowMode} in statement`, function () {
testCase.statementRowModes.forEach(statementRowMode => {
describe(`rowMode ${statementRowMode.type} in statement`, function () {

it('stream rows', function (done) {
const stmt = connection.execute({
sqlText: sql,
rowMode: statementRowMode,
rowMode: statementRowMode.type,
streamResult: true
});
stmt.streamRows()
.on('data', function (row) {
assert.deepStrictEqual(row, expectedResult(connectionRowMode, statementRowMode));
assert.deepStrictEqual(row, statementRowMode.expected);
})
.on('end', function () {
done();
Expand All @@ -62,13 +107,13 @@ describe('Test row mode', function () {
it('fetch rows', function (done) {
connection.execute({
sqlText: sql,
rowMode: statementRowMode,
rowMode: statementRowMode.type,
streamResult: false,
complete: function (err, stmt, rows) {
if (err) {
done(err);
}
assert.deepStrictEqual(rows[0], expectedResult(connectionRowMode, statementRowMode));
assert.deepStrictEqual(rows[0], statementRowMode.expected);
done();
}
});
Expand Down Expand Up @@ -111,4 +156,46 @@ describe('Test row mode', function () {
}
});
});

describe('test correctly named columns for duplicates', function () {
before(function (done) {
connection = testUtil.createConnection();
testUtil.connect(connection, done);
});
after(function (done) {
testUtil.destroyConnection(connection, done);
});

it('test duplicates', function (done) {
const expected = {
"KEY": "a",
"FOO": 1,
"KEY_1": "a1",
"FOO_2": 2,
"KEY_3": "a2",
"FOO_3": 3,
"KEY_2": "a3",
"FOO_4": 4,
"KEY_4": "a4",
"FOO_5": 5
}
connection.execute({
rowMode: RowMode.OBJECT_WITH_INDEXED_COLUMN_NAMES,
sqlText: `select *
from (select 'a' as key, 1 as foo,
'a1' as key_1, 2 as foo,
'a2' as key_3, 3 as foo_3,
'a3' as key, 4 as foo,
'a4' as key, 5 as foo) as table1
`,
complete: function (err, stmt, rows) {
if (err) {
done(err);
}
assert.deepStrictEqual(rows[0], expected);
done();
}
});
});
});
});

0 comments on commit 2e1b2a9

Please sign in to comment.