@itwin/presentation-shared
Version:
The package contains types and utilities used across different iTwin.js Presentation packages.
250 lines • 10.6 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Id64 } from "@itwin/core-bentley";
import { getClass } from "../Metadata.js";
import { PrimitiveValue } from "../Values.js";
/** @public */
// eslint-disable-next-line @typescript-eslint/no-redeclare
export var TypedValueSelectClauseProps;
(function (TypedValueSelectClauseProps) {
function isPrimitiveValue(props) {
return "value" in props;
}
TypedValueSelectClauseProps.isPrimitiveValue = isPrimitiveValue;
function isPrimitiveValueSelector(props) {
return "selector" in props;
}
TypedValueSelectClauseProps.isPrimitiveValueSelector = isPrimitiveValueSelector;
})(TypedValueSelectClauseProps || (TypedValueSelectClauseProps = {}));
/**
* A function for a creating a `TypedPrimitiveValueSelectorProps` object from given primitive ECProperty information.
* @throws Error if the property is not found, is not primitive or has unsupported primitive type (Binary, IGeometry).
* @public
*/
export async function createPrimitivePropertyValueSelectorProps({ schemaProvider, propertyClassAlias, propertyClassName, propertyName, }) {
const ecClass = await getClass(schemaProvider, propertyClassName);
const property = await ecClass.getProperty(propertyName);
if (!property) {
throw new Error(`The property "${propertyName}" not found in class "${propertyClassName}".`);
}
const propertySelector = createRawPropertyValueSelector(propertyClassAlias, propertyName);
if (property.isNavigation()) {
return { selector: `${propertySelector}.[Id]`, type: "Id" };
}
if (!property.isPrimitive()) {
throw new Error(`The property "${propertyName}" should be of either navigation or primitive type.`);
}
const propertyValueType = property.primitiveType;
const extendedType = property.extendedTypeName;
switch (propertyValueType) {
case "IGeometry":
throw new Error(`The property "${propertyName}" of type "IGeometry" is not supported.`);
case "Binary":
if (extendedType === "BeGuid") {
return { selector: `GuidToStr(${propertySelector})`, type: "String" };
}
throw new Error(`The property "${propertyName}" of type "Binary" is not supported.`);
case "Point2d":
return {
selector: `json_object('x', ${propertySelector}.[x], 'y', ${propertySelector}.[y])`,
type: "Point2d",
...(extendedType ? { extendedType } : /* c8 ignore next */ {}),
};
case "Point3d":
return {
selector: `json_object('x', ${propertySelector}.[x], 'y', ${propertySelector}.[y], 'z', ${propertySelector}.[z])`,
type: "Point3d",
...(extendedType ? { extendedType } : /* c8 ignore next */ {}),
};
case "Double":
const koqName = (await property.kindOfQuantity)?.fullName;
return {
selector: propertySelector,
type: "Double",
...(extendedType ? { extendedType } : /* c8 ignore next */ {}),
...(koqName ? { koqName } : /* c8 ignore next */ {}),
};
}
return {
selector: propertySelector,
type: propertyValueType,
...(extendedType ? { extendedType } : /* c8 ignore next */ {}),
};
}
/**
* Creates an ECSQL selector for raw property value, or, optionally - it's component. Example result:
* `[classAlias].[propertyName].[componentName]`.
*
* @public
*/
export function createRawPropertyValueSelector(classAlias, propertyName, componentName) {
let propertySelector = `[${classAlias}].[${propertyName}]`;
if (componentName) {
propertySelector += `.[${componentName}]`;
}
return propertySelector;
}
/**
* Creates an ECSQL selector for a raw primitive value.
* - `undefined` is selected as `NULL`.
* - `Date` values are selected in julian day format.
* - `Point2d` and `Point3d` values are selected as serialized JSON objects, e.g. `{ x: 1, y: 2, z: 3 }`.
* - Other kinds of values are selected as-is.
*
* @public
*/
export function createRawPrimitiveValueSelector(value) {
if (value === undefined) {
return "NULL";
}
if (value instanceof Date) {
return `julianday('${value.toISOString()}')`;
}
if (PrimitiveValue.isPoint3d(value)) {
return `json_object('x', ${value.x}, 'y', ${value.y}, 'z', ${value.z})`;
}
if (PrimitiveValue.isPoint2d(value)) {
return `json_object('x', ${value.x}, 'y', ${value.y})`;
}
switch (typeof value) {
case "string":
return Id64.isId64(value) ? value : `'${value}'`;
case "number":
return value.toString();
case "boolean":
return value ? "TRUE" : "FALSE";
}
}
/**
* Creates an ECSQL selector that results in a stringified `InstanceKey` object.
* @public
*/
export function createInstanceKeySelector(props) {
const classIdSelector = `[${props.alias}].[ECClassId]`;
const instanceHexIdSelector = `IdToHex([${props.alias}].[ECInstanceId])`;
return `json_object('className', ec_classname(${classIdSelector}, 's.c'), 'id', ${instanceHexIdSelector})`;
}
/**
* Creates a clause for returning `NULL` when `checkSelector` returns a falsy value, or result of `valueSelector`
* otherwise. Example result: `IIF(CHECK_SELECTOR, VALUE_SELECTOR, NULL)`.
*
* @note In SQL `NULL` is not considered falsy, so when checking for `NULL` values, the `checkSelector` should
* be like `{selector} IS NOT NULL` rather than just `${selector}`.
*
* @public
*/
export function createNullableSelector(props) {
return `IIF(${props.checkSelector}, ${props.valueSelector}, NULL)`;
}
/**
* Create an ECSQL selector combined of multiple typed value selectors in a form of a JSON array. This allows handling results
* of each value selector individually when parsing query result.
*
* Example result: `json_array(VALUE_SELECTOR_1, VALUE_SELECTOR_2, ...)`.
*
* Optionally, the function also accepts a `checkSelector` argument, which can be used to make the selector return
* `NULL` result when the argument selector results in `NULL`. Example result with `checkSelector`:
* `IIF(CHECK_SELECTOR, json_array(VALUE_SELECTOR_1, VALUE_SELECTOR_2, ...), NULL)`.
*
* @note The resulting JSON is of `ConcatenatedValue` type and it's recommended to use `ConcatenatedValue.serialize` to
* handle each individual part.
*
* @see `ConcatenatedValue`
*
* @public
*/
export function createConcatenatedValueJsonSelector(selectors, checkSelector) {
const combinedSelectors = `json_array(${selectors.map((sel) => createTypedValueJsonSelector(sel)).join(", ")})`;
if (checkSelector) {
return createNullableSelector({ checkSelector, valueSelector: combinedSelectors });
}
return combinedSelectors;
}
function createTypedValueJsonSelector(props) {
let valueSelector;
if (TypedValueSelectClauseProps.isPrimitiveValue(props)) {
valueSelector = createPrimitiveValueJsonSelector(props.value);
}
else if (props.type) {
valueSelector = props.selector;
}
else {
return props.selector;
}
const args = {
value: valueSelector,
type: `'${props.type}'`,
...(props.extendedType ? { extendedType: `'${props.extendedType}'` } : {}),
...(props.type === "Double" && props.koqName ? { koqName: `'${props.koqName}'` } : {}),
};
return `
json_object(${Object.entries(args)
.map(([key, value]) => `'${key}', ${value}`)
.join(", ")})
`;
}
function createPrimitiveValueJsonSelector(value) {
if (value instanceof Date) {
return `'${value.toISOString()}'`;
}
if (PrimitiveValue.isPoint3d(value)) {
return `json_object('x', ${value.x}, 'y', ${value.y}, 'z', ${value.z})`;
}
if (PrimitiveValue.isPoint2d(value)) {
return `json_object('x', ${value.x}, 'y', ${value.y})`;
}
switch (typeof value) {
case "string":
return Id64.isId64(value) ? value : `'${value}'`;
case "number":
return value.toString();
case "boolean":
return value ? "TRUE" : "FALSE";
}
}
/**
* Create an ECSQL selector combined of multiple typed value selectors in a form of a string.
*
* Example result: `VALUE_SELECTOR_1 || VALUE_SELECTOR_2 || ...`.
*
* Optionally, the function also accepts a `checkSelector` argument, which can be used to make the selector return
* `NULL` result when `checkSelector` results in a falsy value. Example result with `checkSelector`:
* `IIF(CHECK_SELECTOR, VALUE_SELECTOR_1 || VALUE_SELECTOR_2 || ..., NULL)`.
*
* @note Not all types of `TypedValueSelectClauseProps` can be serialized to a user-friendly string, e.g. when
* selecting a numeric value with units, this function is going to select the raw value. To create properly formatted
* concatenated values:
* 1. They should be selected with `createConcatenatedValueJsonSelector`, which returns a serialized JSON object.
* 2. The JSON should be parsed from resulting string and passed to `ConcatenatedValue.serialize`, which additionally
* takes a formatter. One can be created using `createDefaultValueFormatter`.
*
* @public
*/
export function createConcatenatedValueStringSelector(selectors, checkSelector) {
const combinedSelectors = selectors.length ? selectors.map((sel) => createTypedValueStringSelector(sel)).join(" || ") : "''";
if (checkSelector) {
return createNullableSelector({ checkSelector, valueSelector: combinedSelectors });
}
return combinedSelectors;
}
function createTypedValueStringSelector(props) {
if (TypedValueSelectClauseProps.isPrimitiveValueSelector(props)) {
return `CAST(${props.selector} AS TEXT)`;
}
return createPrimitiveValueStringSelector(props.value);
}
function createPrimitiveValueStringSelector(value) {
if (value instanceof Date) {
return `'${value.toLocaleString()}'`;
}
if (PrimitiveValue.isPoint3d(value)) {
return `'(${value.x}, ${value.y}, ${value.z})'`;
}
if (PrimitiveValue.isPoint2d(value)) {
return `'(${value.x}, ${value.y})'`;
}
return `'${value.toString()}'`;
}
//# sourceMappingURL=ECSqlValueSelectorSnippets.js.map