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: add missing function mappings #945

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
6 changes: 3 additions & 3 deletions db-service/lib/cql-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,16 +224,16 @@ const StandardFunctions = {

// odata spec defines the value format for totalseconds as a duration like: P12DT23H59M59.999999999999S
// P -> duration indicator
// D -> days, T -> Time seperator, H -> hours, M -> minutes, S -> fractional seconds
// D -> days, T -> Time separator, H -> hours, M -> minutes, S -> fractional seconds
// By splitting the DT and calculating the seconds of the time separate from the day
// it possible to determine the full amount of seconds by adding them together as fractionals and multiplying
// the number of seconds in a day
// As sqlite is most accurate with juliandays it is better to do then then using actual second function
// while the odata specification states that the seconds has to be fractional which only julianday allows
/**
* Generates SQL statement that produces an OData compliant duration string like: P12DT23H59M59.999999999999S
* Generates SQL statement that produces a number for a OData compliant duration string like: P12DT23H59M59.999999999999S
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure what happened with this sentence, but it should be something more like:

Suggested change
* Generates SQL statement that produces a number for a OData compliant duration string like: P12DT23H59M59.999999999999S
* Generates a SQL statement that processes an OData compliant duration string: P12DT23H59M59.999999999999S

Copy link
Member Author

Choose a reason for hiding this comment

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

that makes more sense, however why not state what the function returns as well?

* @param {string} x
* @returns {string}
* @returns {number}
*/
totalseconds: x => `(
(
Expand Down
40 changes: 39 additions & 1 deletion hana/lib/cql-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,45 @@ const StandardFunctions = {
maxdatetime: () => "'9999-12-31T23:59:59.999Z'",
mindatetime: () => "'0001-01-01T00:00:00.000Z'",
now: () => `session_context('$now')`,
fractionalseconds: x => `(TO_DECIMAL(SECOND(${x}),5,3) - TO_INTEGER(SECOND(${x})))`
fractionalseconds: x => `(TO_DECIMAL(SECOND(${x}),5,3) - TO_INTEGER(SECOND(${x})))`,
totalseconds: x => {
// 1. Extract and convert days to seconds
// 2. Extract and convert hours to seconds
// 3. Extract and convert minutes to seconds
// 4. Extract seconds (including fractional part) and convert to double
// --> Add all together
return `(
CAST(
SUBSTRING(${x}, 2, LOCATE(${x}, 'DT') - 2
) AS INTEGER) * 86400
) + (
(
CAST(
SUBSTRING(
${x},
LOCATE(${x}, 'DT') + 2,
LOCATE(${x}, 'H') - LOCATE(${x}, 'DT') - 2
) AS INTEGER
) * 3600
) + (
CAST(
SUBSTRING(
${x},
LOCATE(${x}, 'H') + 1,
LOCATE(${x}, 'M') - LOCATE(${x}, 'H') - 1
) AS INTEGER
) * 60
) + (
CAST(
SUBSTRING(
${x},
LOCATE(${x}, 'M') + 1,
LOCATE(${x}, 'S') - LOCATE(${x}, 'M') - 1
) AS DOUBLE
)
)
)`;
},
}

const HANAFunctions = {
Expand Down
31 changes: 14 additions & 17 deletions postgres/lib/cql-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,26 @@ const StandardFunctions = {
month: x => `date_part('month', ${castVal(x)})`,
day: x => `date_part('day', ${castVal(x)})`,
time: x => `to_char(${castVal(x)}, 'HH24:MI:SS')`,
date: x => `to_char(${castVal(x)}, 'YYYY-MM-DD')`,
hour: x => `date_part('hour', ${castVal(x)})`,
minute: x => `date_part('minute', ${castVal(x)})`,
second: x => `floor(date_part('second', ${castVal(x)}))`,
fractionalseconds: x => `CAST(date_part('second', ${castVal(x)}) - floor(date_part('second', ${castVal(x)})) AS DECIMAL)`,
// 1. Extract and convert days to seconds
// 2. Extract and convert hours to seconds
// 3. Extract and convert minutes to seconds
// 4. Extract seconds (including fractional part) and convert to double
// --> Add all together
totalseconds: x => `(
CAST(substring(${x}, 2, strpos(${x}, 'DT') - 2) AS INTEGER) * 86400
) + (
(
(
Copy link
Member Author

Choose a reason for hiding this comment

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

didnt work before, because we already have seconds from the EXTRACT(EPOCH) step, so multiplying by 86400 again drastically inflates the result.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why no just move the multiplication and keep the EXTRACT and replace syntax ? As the new split_part syntax repeats the same string functions over and over again.

CAST(substring(${x},2,strpos(${x},'DT') - 2) AS INTEGER)
) + (
EXTRACT (EPOCH FROM
CAST(
replace(
replace(
replace(
substring(${x},strpos(${x},'DT') + 2),
'H',':'
),'M',':'
),'S','Z'
)
as TIME)
) - 0.5
)
) * 86400
CAST(split_part(substring(${x}, strpos(${x}, 'DT') + 2), 'H', 1) AS INTEGER) * 3600
) + (
CAST(split_part(split_part(substring(${x}, strpos(${x}, 'DT') + 2), 'H', 2), 'M', 1) AS INTEGER) * 60
) + (
CAST(replace(split_part(split_part(substring(${x}, strpos(${x}, 'DT') + 2), 'M', 2), 'S', 1), 'Z', '') AS DOUBLE PRECISION)
)
)`,
now: function() {
return this.session_context({val: '$now'})
Expand Down
2 changes: 1 addition & 1 deletion test/compliance/SELECT.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,7 @@ describe('SELECT', () => {
{ xpr: [ref, op, SELECT(ref).from(targetName)] },
{ xpr: [{ list: [ref] }, op, SELECT(ref).from(targetName)] },
{ xpr: [{ list: [ref, ref] }, op, SELECT([{ ...ref, as: 'a' }, { ...ref, as: 'b' }]).from(targetName)] },
// Repreating the previous statements replaceing ref with null
// Repeating the previous statements replacing ref with null
{ xpr: [unified.null, op, { list: [ref] }] },
{ xpr: [unified.null, op, { list: [ref, ref] }] },
{ xpr: [{ list: [unified.null] }, op, { list: [{ list: [ref] }] }] },
Expand Down
17 changes: 17 additions & 0 deletions test/compliance/functions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1261,4 +1261,21 @@ describe('functions', () => {
}
})
})

describe('odata', () => {
patricebender marked this conversation as resolved.
Show resolved Hide resolved
test('totalseconds', async () => {
const cqn = SELECT.one
.from('edge.hana.functions.timestamps')
.columns("totalseconds('P12DT23H59M59.999999999999S') as ts")
const res = await cds.run(cqn)
expect(res).to.have.property('ts').that.equals(1123200)
})

test('date', async () => {
const cqn = SELECT.one.from('edge.hana.functions.timestamps').columns('date(a) as dateOnly', 'a as fullTimestamp')
const res = await cds.run(cqn)
expect(res).to.have.property('fullTimestamp').that.equals('2627-10-10T23:00:00.000Z')
expect(res).to.have.property('dateOnly').that.equals('2627-10-10')
})
})
})
Loading