├── .bazelproject ├── .bazelrc ├── .github └── workflows │ ├── build.yaml │ └── publish.yaml ├── .gitignore ├── AUTHORS ├── CONTRIBUTING.md ├── GUIDELINES.md ├── LICENSE ├── MODULE.bazel ├── README.md ├── WORKSPACE.bazel ├── aspect ├── BUILD ├── pom.xml └── src │ └── main │ └── java │ └── tech │ └── ydb │ └── yoj │ └── aspect │ └── tx │ ├── YojTransactionAspect.java │ └── YojTransactional.java ├── bom ├── BUILD └── pom.xml ├── checkstyle-suppressions.xml ├── checkstyle.xml ├── databind ├── BUILD ├── pom.xml └── src │ ├── main │ └── java │ │ └── tech │ │ └── ydb │ │ └── yoj │ │ ├── DeprecationWarnings.java │ │ ├── ExperimentalApi.java │ │ ├── InternalApi.java │ │ └── databind │ │ ├── ByteArray.java │ │ ├── CustomValueType.java │ │ ├── CustomValueTypes.java │ │ ├── DbType.java │ │ ├── FieldValueType.java │ │ ├── converter │ │ ├── EnumOrdinalConverter.java │ │ ├── ObjectColumn.java │ │ ├── StringColumn.java │ │ ├── StringValueConverter.java │ │ ├── StringValueType.java │ │ └── ValueConverter.java │ │ ├── expression │ │ ├── AndExpr.java │ │ ├── FieldValue.java │ │ ├── FilterBuilder.java │ │ ├── FilterExpression.java │ │ ├── IllegalExpressionException.java │ │ ├── LeafExpression.java │ │ ├── ListExpr.java │ │ ├── ModelField.java │ │ ├── NotExpr.java │ │ ├── NullExpr.java │ │ ├── OrExpr.java │ │ ├── OrderBuilder.java │ │ ├── OrderExpression.java │ │ ├── ScalarExpr.java │ │ ├── values │ │ │ ├── BooleanFieldValue.java │ │ │ ├── ByteArrayFieldValue.java │ │ │ ├── FieldValue.java │ │ │ ├── IntegerFieldValue.java │ │ │ ├── RealFieldValue.java │ │ │ ├── StringFieldValue.java │ │ │ ├── TimestampFieldValue.java │ │ │ ├── Tuple.java │ │ │ ├── TupleFieldValue.java │ │ │ └── UuidFieldValue.java │ │ └── visitor │ │ │ ├── AllMatch.java │ │ │ ├── AnyMatch.java │ │ │ └── RemoveIf.java │ │ └── schema │ │ ├── BindingException.java │ │ ├── Changefeed.java │ │ ├── Changefeeds.java │ │ ├── Column.java │ │ ├── ConstructionException.java │ │ ├── CustomConverterException.java │ │ ├── CustomValueTypeInfo.java │ │ ├── FieldValueException.java │ │ ├── GlobalIndex.java │ │ ├── GlobalIndexes.java │ │ ├── ObjectSchema.java │ │ ├── Schema.java │ │ ├── TTL.java │ │ ├── Table.java │ │ ├── configuration │ │ └── SchemaRegistry.java │ │ ├── naming │ │ ├── AnnotationFirstNamingStrategy.java │ │ ├── DelegateNamingStrategy.java │ │ └── NamingStrategy.java │ │ └── reflect │ │ ├── KotlinDataClassComponent.java │ │ ├── KotlinDataClassType.java │ │ ├── KotlinDataClassTypeFactory.java │ │ ├── KotlinReflectionDetector.java │ │ ├── PojoField.java │ │ ├── PojoType.java │ │ ├── RecordField.java │ │ ├── RecordType.java │ │ ├── ReflectField.java │ │ ├── ReflectFieldBase.java │ │ ├── ReflectType.java │ │ ├── Reflector.java │ │ ├── SimpleType.java │ │ └── StdReflector.java │ └── test │ ├── java │ └── tech │ │ └── ydb │ │ └── yoj │ │ └── databind │ │ ├── ByteArrayTest.java │ │ ├── expression │ │ ├── ComplexObjectFilterExpressionTest.java │ │ └── FilterExpressionTest.java │ │ └── schema │ │ ├── ChangefeedSchemaTest.java │ │ ├── HybridSchemaTest.java │ │ ├── PojoSchemaTest.java │ │ ├── RecordSchemaTest.java │ │ ├── TtlSchemaTest.java │ │ └── naming │ │ ├── AnnotatedEntity.java │ │ ├── AnnotationFirstNamingStrategyTest.java │ │ ├── BaseNamingStrategyTestBase.java │ │ ├── ColumnTest.java │ │ ├── MixedEntity.java │ │ ├── SingleFieldEntity.java │ │ ├── TableTest.java │ │ └── TestEntity.java │ ├── kotlin │ └── tech │ │ └── ydb │ │ └── yoj │ │ └── databind │ │ └── schema │ │ ├── KotlinJvmRecordSchemaTest.kt │ │ └── KotlinSchemaTest.kt │ └── resources │ └── log4j2.yaml ├── ext-meta-generator ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── tech │ │ │ └── ydb │ │ │ └── yoj │ │ │ └── generator │ │ │ ├── FieldGeneratorAnnotationProcessor.java │ │ │ ├── FieldInfo.java │ │ │ ├── SourceClassStructure.java │ │ │ ├── StringConstantsRenderer.java │ │ │ ├── TargetClassStructure.java │ │ │ └── Utils.java │ └── resources │ │ └── log4j2.yaml │ └── test │ ├── java │ └── tech │ │ └── ydb │ │ └── yoj │ │ └── generator │ │ └── FieldGeneratorAnnotationProcessorTest.java │ └── resources │ ├── input │ ├── ComplexNesting.java │ ├── EntityWithComplexSingularId.java │ ├── EntityWithSimpleSingularId.java │ ├── KotlinDataClass.kt │ ├── NestedClass.java │ ├── NoSimpleFieldsClass.java │ ├── NonEntityClass.java │ ├── TestRecord.java │ ├── TypicalEntity.java │ └── UnconventionalFieldNames.java │ └── output │ ├── ComplexNestingFields.java │ ├── EntityWithComplexSingularIdFields.java │ ├── EntityWithSimpleSingularIdFields.java │ ├── KotlinDataClassFields.java │ ├── NestedClassFields.java │ ├── NoSimpleFieldsClassFields.java │ ├── TestRecordFields.java │ ├── TypicalEntityFields.java │ └── UnconventionalFieldNamesFields.java ├── json-jackson-v2 ├── BUILD ├── pom.xml └── src │ └── main │ └── java │ └── tech │ └── ydb │ └── yoj │ └── repository │ └── db │ └── json │ └── JacksonJsonConverter.java ├── lombok.config ├── pom.xml ├── prepare-next-snapshot.sh ├── prepare-release.sh ├── repository-inmemory ├── BUILD ├── pom.xml └── src │ ├── main │ └── java │ │ └── tech │ │ └── ydb │ │ └── yoj │ │ └── repository │ │ └── test │ │ └── inmemory │ │ ├── Columns.java │ │ ├── InMemoryDataShard.java │ │ ├── InMemoryEntityLine.java │ │ ├── InMemoryRepository.java │ │ ├── InMemoryRepositoryException.java │ │ ├── InMemoryRepositoryTransaction.java │ │ ├── InMemoryStorage.java │ │ ├── InMemoryTable.java │ │ ├── InMemoryTxLockWatcher.java │ │ ├── ReadOnlyTxDataShard.java │ │ ├── TxDataShardImpl.java │ │ └── WriteTxDataShard.java │ └── test │ ├── java │ └── tech │ │ └── ydb │ │ └── yoj │ │ └── repository │ │ └── test │ │ └── inmemory │ │ ├── InMemoryListingTest.java │ │ ├── InMemoryRepositoryTest.java │ │ ├── InMemoryTableQueryBuilderTest.java │ │ └── TestInMemoryRepository.java │ └── resources │ └── log4j2.yaml ├── repository-test ├── BUILD ├── pom.xml └── src │ └── main │ └── java │ └── tech │ └── ydb │ └── yoj │ └── repository │ └── test │ ├── ListingTest.java │ ├── RepositoryTest.java │ ├── RepositoryTestSupport.java │ ├── TableQueryBuilderTest.java │ ├── entity │ └── TestEntities.java │ └── sample │ ├── TeamView.java │ ├── TestDb.java │ ├── TestDbImpl.java │ ├── TestEntityOperations.java │ └── model │ ├── Book.java │ ├── Bubble.java │ ├── BytePkEntity.java │ ├── ChangefeedEntity.java │ ├── Complex.java │ ├── DetachedEntity.java │ ├── DetachedEntityId.java │ ├── EntityWithTopLevelId.java │ ├── EntityWithValidation.java │ ├── IndexedEntity.java │ ├── LogEntry.java │ ├── MultiLevelDirectory.java │ ├── MultiWrappedEntity.java │ ├── MultiWrappedEntity2.java │ ├── NetworkAppliance.java │ ├── NonDeserializableEntity.java │ ├── NonDeserializableObject.java │ ├── Primitive.java │ ├── Project.java │ ├── Referring.java │ ├── Simple.java │ ├── Supabubble.java │ ├── Supabubble2.java │ ├── Team.java │ ├── TopLevelId.java │ ├── TtlEntity.java │ ├── TypeFreak.java │ ├── UniqueProject.java │ ├── UpdateFeedEntry.java │ ├── Version.java │ ├── VersionColumn.java │ ├── VersionedAliasedEntity.java │ ├── VersionedEntity.java │ ├── WithUnflattenableField.java │ └── annotations │ ├── Digest.java │ ├── Sha256.java │ ├── UniqueEntity.java │ ├── UniqueEntityNative.java │ └── YojString.java ├── repository-ydb-common ├── BUILD ├── pom.xml └── src │ └── main │ └── java │ └── tech │ └── ydb │ └── yoj │ └── repository │ └── ydb │ ├── exception │ ├── BadSessionException.java │ ├── ResultTruncatedException.java │ ├── SnapshotCreateException.java │ ├── UnexpectedException.java │ ├── YdbClientInternalException.java │ ├── YdbComponentUnavailableException.java │ ├── YdbOverloadedException.java │ ├── YdbRepositoryException.java │ ├── YdbSchemaException.java │ ├── YdbSchemaPathNotFoundException.java │ ├── YdbUnauthenticatedException.java │ └── YdbUnauthorizedException.java │ └── metrics │ └── GaugeSupplierCollector.java ├── repository-ydb-v2 ├── BUILD ├── pom.xml └── src │ ├── main │ └── java │ │ └── tech │ │ └── ydb │ │ └── yoj │ │ └── repository │ │ └── ydb │ │ ├── LazyGrpcTransport.java │ │ ├── YdbConfig.java │ │ ├── YdbLegacySpliterator.java │ │ ├── YdbOperations.java │ │ ├── YdbRepository.java │ │ ├── YdbRepositoryTransaction.java │ │ ├── YdbSpliterator.java │ │ ├── bulk │ │ ├── BulkMapper.java │ │ └── BulkMapperImpl.java │ │ ├── client │ │ ├── QueryInterceptingSession.java │ │ ├── QueryInterceptor.java │ │ ├── ResultSetConverter.java │ │ ├── SessionManager.java │ │ ├── YdbConverter.java │ │ ├── YdbIssue.java │ │ ├── YdbPaths.java │ │ ├── YdbSchemaOperations.java │ │ ├── YdbSessionManager.java │ │ ├── YdbTableHint.java │ │ ├── YdbValidator.java │ │ └── interceptors │ │ │ ├── CommonFullScanCallback.java │ │ │ └── FullScanDetector.java │ │ ├── compatibility │ │ ├── YdbDataCompatibilityChecker.java │ │ └── YdbSchemaCompatibilityChecker.java │ │ ├── merge │ │ ├── ByEntityYqlQueriesMerger.java │ │ ├── QueriesMerger.java │ │ └── YqlQueriesMerger.java │ │ ├── readtable │ │ ├── EntityIdKeyMapper.java │ │ └── ReadTableMapper.java │ │ ├── statement │ │ ├── Count.java │ │ ├── CountAllStatement.java │ │ ├── DeleteAllStatement.java │ │ ├── DeleteByIdStatement.java │ │ ├── FindAllYqlStatement.java │ │ ├── FindInStatement.java │ │ ├── FindRangeStatement.java │ │ ├── FindStatement.java │ │ ├── FindYqlStatement.java │ │ ├── InsertYqlStatement.java │ │ ├── MultipleVarsYqlStatement.java │ │ ├── PredicateStatement.java │ │ ├── ResultSetReader.java │ │ ├── Statement.java │ │ ├── UpdateByIdStatement.java │ │ ├── UpdateInStatement.java │ │ ├── UpdateModel.java │ │ ├── UpdateSetParam.java │ │ ├── UpsertYqlStatement.java │ │ ├── YqlStatement.java │ │ └── YqlStatementParam.java │ │ ├── table │ │ ├── BatchFindSpliterator.java │ │ └── YdbTable.java │ │ └── yql │ │ ├── YqlCompositeType.java │ │ ├── YqlLimit.java │ │ ├── YqlListingQuery.java │ │ ├── YqlOrderBy.java │ │ ├── YqlPredicate.java │ │ ├── YqlPredicateParam.java │ │ ├── YqlPrimitiveType.java │ │ ├── YqlStatementPart.java │ │ ├── YqlType.java │ │ └── YqlView.java │ └── test │ ├── java │ └── tech │ │ └── ydb │ │ └── yoj │ │ └── repository │ │ └── ydb │ │ ├── NonSerializableEntity.java │ │ ├── NonSerializableObject.java │ │ ├── SubdirEntity.java │ │ ├── TestYdbConfig.java │ │ ├── TestYdbRepository.java │ │ ├── YdbEnvAndTransportRule.java │ │ ├── YdbRepositoryCacheTest.java │ │ ├── YdbRepositoryIntegrationTest.java │ │ ├── YdbSpliteratorTest.java │ │ ├── YqlLimitTest.java │ │ ├── YqlPredicateTest.java │ │ ├── YqlTypeLegacyTest.java │ │ ├── YqlTypeRecommendedTest.java │ │ ├── list │ │ └── YdbListingIntegrationTest.java │ │ ├── merge │ │ ├── QueriesMergerTest.java │ │ └── YqlQueryMergerIntegrationTest.java │ │ ├── model │ │ ├── EntityChangeTtl.java │ │ ├── EntityDropTtl.java │ │ ├── IndexedEntityChangeIndex.java │ │ ├── IndexedEntityCreateIndex.java │ │ ├── IndexedEntityDropIndex.java │ │ └── IndexedEntityNew.java │ │ ├── query │ │ └── YdbTableQueryBuilderIntegrationTest.java │ │ ├── sample │ │ └── model │ │ │ ├── HintAutoPartitioningByLoad.java │ │ │ ├── HintInt64Range.java │ │ │ ├── HintTablePreset.java │ │ │ └── HintUniform.java │ │ ├── statement │ │ ├── AbstractMultipleVarsYqlStatementIntegrationTestBase.java │ │ ├── DeleteByIdStatementIntegrationTest.java │ │ ├── FindInStatementTest.java │ │ ├── InsertYqlStatementIntegrationTest.java │ │ └── UpsertYqlStatementIntegrationTest.java │ │ ├── util │ │ └── RandomUtils.java │ │ └── yql │ │ ├── YqlListingQueryTest.java │ │ ├── YqlTypeAllTypesLegacyMappingTest.java │ │ └── YqlTypeAllTypesRecommendedMappingTest.java │ ├── resources │ └── log4j2.yaml │ └── script │ └── run-ydb.sh ├── repository ├── BUILD ├── pom.xml └── src │ ├── main │ └── java │ │ └── tech │ │ └── ydb │ │ └── yoj │ │ └── repository │ │ ├── BaseDb.java │ │ ├── DbTypeQualifier.java │ │ └── db │ │ ├── AbstractDelegatingTable.java │ │ ├── DelegatingTxManager.java │ │ ├── Entity.java │ │ ├── EntityExpressions.java │ │ ├── EntityIdSchema.java │ │ ├── EntityList.java │ │ ├── EntitySchema.java │ │ ├── IsolationLevel.java │ │ ├── NonTx.java │ │ ├── Range.java │ │ ├── RecordEntity.java │ │ ├── Repository.java │ │ ├── RepositoryTransaction.java │ │ ├── SchemaOperations.java │ │ ├── StdTxManager.java │ │ ├── Table.java │ │ ├── TableDescriptor.java │ │ ├── TableQueryBuilder.java │ │ ├── TableQueryImpl.java │ │ ├── Tx.java │ │ ├── TxImpl.java │ │ ├── TxManager.java │ │ ├── TxManagerState.java │ │ ├── TxOptions.java │ │ ├── ViewSchema.java │ │ ├── bulk │ │ └── BulkParams.java │ │ ├── cache │ │ ├── DbValueUpdater.java │ │ ├── EmptyFirstLevelCache.java │ │ ├── EmptyRepositoryCache.java │ │ ├── FirstLevelCache.java │ │ ├── FirstLevelCacheImpl.java │ │ ├── FirstLevelCacheProvider.java │ │ ├── RepositoryCache.java │ │ ├── RepositoryCacheImpl.java │ │ ├── TransactionLocal.java │ │ └── TransactionLog.java │ │ ├── common │ │ ├── CommonConverters.java │ │ └── JsonConverter.java │ │ ├── exception │ │ ├── AggregateRepositoryException.java │ │ ├── ConversionException.java │ │ ├── CreateTableException.java │ │ ├── DeadlineExceededException.java │ │ ├── DropTableException.java │ │ ├── EntityAlreadyExistsException.java │ │ ├── IllegalTransactionException.java │ │ ├── IllegalTransactionIsolationLevelException.java │ │ ├── IllegalTransactionScanException.java │ │ ├── InternalRepositoryException.java │ │ ├── OptimisticLockException.java │ │ ├── QueryCancelledException.java │ │ ├── QueryInterruptedException.java │ │ ├── RepositoryException.java │ │ ├── RetryableException.java │ │ └── UnavailableException.java │ │ ├── list │ │ ├── BadListingException.java │ │ ├── GenericListResult.java │ │ ├── InMemoryQueries.java │ │ ├── ListRequest.java │ │ ├── ListResult.java │ │ ├── ViewListResult.java │ │ └── token │ │ │ ├── EmptyPageToken.java │ │ │ ├── FallbackPageToken.java │ │ │ └── PageToken.java │ │ ├── projection │ │ ├── ProjectionCache.java │ │ ├── ProjectionMappings.java │ │ ├── Projections.java │ │ ├── RoProjectionCache.java │ │ └── RwProjectionCache.java │ │ ├── readtable │ │ └── ReadTableParams.java │ │ └── statement │ │ └── Changeset.java │ └── test │ ├── java │ └── tech │ │ └── ydb │ │ └── yoj │ │ └── repository │ │ ├── PojoSchemaTest.java │ │ ├── RecordSchemaTest.java │ │ ├── db │ │ ├── PojoEntitySchemaTest.java │ │ ├── PojoEntityTest.java │ │ ├── RecordEntitySchemaTest.java │ │ ├── RecordEntityTest.java │ │ ├── StdTxManagerTest.java │ │ ├── StrictTxManagerTest.java │ │ ├── TxOptionsTest.java │ │ ├── cache │ │ │ ├── DbValueUpdaterTest.java │ │ │ └── FirstLevelCacheTest.java │ │ └── testcaller │ │ │ └── TestDbTxCaller.java │ │ ├── hybrid │ │ ├── AccountSnapshotMetadata.java │ │ └── JobArgs.java │ │ └── testcaller │ │ └── TestTxCaller.java │ ├── kotlin │ └── tech │ │ └── ydb │ │ └── yoj │ │ └── repository │ │ └── hybrid │ │ ├── Account.kt │ │ ├── HybridSchemaTest.kt │ │ └── Job.kt │ └── resources │ └── log4j2.yaml └── util ├── BUILD ├── pom.xml └── src ├── main └── java │ └── tech │ └── ydb │ └── yoj │ └── util │ ├── function │ ├── MoreSuppliers.java │ └── StreamSupplier.java │ ├── lang │ ├── Annotations.java │ ├── BetterCollectors.java │ ├── CallStack.java │ ├── Exceptions.java │ ├── Interrupts.java │ ├── Proxies.java │ ├── Strings.java │ └── UncheckedInterruptedException.java │ └── retry │ ├── ExponentialBackoffRetryPolicy.java │ ├── FixedDelayRetryPolicy.java │ └── RetryPolicy.java └── test └── java └── tech └── ydb └── yoj └── util ├── function └── StreamSupplierTest.java └── lang ├── BetterCollectorsTest.java ├── ExceptionsTest.java └── StringsTest.java /.bazelproject: -------------------------------------------------------------------------------- 1 | workspace_type: java 2 | 3 | java_language_level: 21 4 | 5 | directories: 6 | . 7 | -databind/pom.xml 8 | -repository/pom.xml 9 | -json-jackson-v2/pom.xml 10 | -repository-inmemory/pom.xml 11 | -repository-test/pom.xml 12 | -repository-ydb-common/pom.xml 13 | -repository-ydb-v2/pom.xml 14 | -tx-aspect/pom.xml 15 | -util/pom.xml 16 | 17 | test_sources: 18 | databind/src/test 19 | repository/src/test 20 | repository-inmemory/src/test 21 | repository-ydb-v2/src/test 22 | util/src/test 23 | 24 | # Automatically includes all relevant targets under the 'directories' above 25 | derive_targets_from_directories: true 26 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | common --enable_bzlmod 2 | 3 | # java common flags 4 | common --java_language_version=21 5 | common --java_runtime_version=remotejdk_21 6 | common --tool_java_language_version=21 7 | common --tool_java_runtime_version=remotejdk_21 8 | 9 | # java build flags 10 | build --nojava_header_compilation 11 | #build --strict_java_deps error 12 | 13 | # Don't depend on a JAVA_HOME pointing at a system JDK, see https://github.com/bazelbuild/rules_jvm_external/issues/445 14 | build --repo_env=JAVA_HOME=../bazel_tools/jdk 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Inspired by https://www.toptal.com/developers/gitignore/api/java,maven,intellij 2 | 3 | # Maven 4 | target/ 5 | pom.xml.tag 6 | pom.xml.releaseBackup 7 | pom.xml.versionsBackup 8 | pom.xml.next 9 | release.properties 10 | dependency-reduced-pom.xml 11 | buildNumber.properties 12 | # https://www.mojohaus.org/flatten-maven-plugin/flatten-mojo.html 13 | .flattened-pom.xml 14 | .mvn/timing.properties 15 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 16 | .mvn/wrapper/maven-wrapper.jar 17 | 18 | # JVM crash logs, @see http://www.java.com/en/download/help/error_hotspot.xml 19 | hs_err_pid* 20 | replay_pid* 21 | 22 | # Debugger something 23 | .attach_pid* 24 | 25 | # IntelliJ project files 26 | .idea/ 27 | *.iml 28 | *.iws 29 | *.ipr 30 | 31 | # Eclipse project files 32 | .project 33 | .classpath 34 | 35 | # NetBeans project files 36 | nbactions.xml 37 | nb-configuration.xml 38 | 39 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Notice to external contributors 2 | 3 | ## Common 4 | 5 | YDB is a free and open project and we appreciate to receive contributions from our community. 6 | 7 | ## Contributing code changes 8 | 9 | If you would like to contribute a new feature or a bug fix, please discuss your idea first on the GitHub issue. 10 | If there is no issue for your idea, please open one. It may be that somebody is already working on it, 11 | or that there are some complex obstacles that you should know about before starting the implementation. 12 | Usually there are several ways to fix a problem and it is important to find the right approach before spending time on a PR 13 | that cannot be merged. 14 | 15 | ## Provide a contribution 16 | 17 | To make a contribution you should submit a pull request. There will probably be discussion about the pull request and, if any changes are needed, we would love to work with you to get your pull request merged. 18 | 19 | ## Other questions 20 | 21 | If you have any questions, please mail us at info@ydb.tech. 22 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Bazel now uses Bzlmod by default to manage external dependencies. 3 | # Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel. 4 | # 5 | # For more details, please check https://github.com/bazelbuild/bazel/issues/18958 6 | ############################################################################### 7 | bazel_dep(name = "rules_java", version = "7.4.0") 8 | bazel_dep(name = "contrib_rules_jvm", version = "0.24.0") 9 | bazel_dep(name = "rules_jvm_external", version = "6.0") 10 | bazel_dep(name = "apple_rules_lint", version = "0.3.2") 11 | -------------------------------------------------------------------------------- /aspect/BUILD: -------------------------------------------------------------------------------- 1 | load("@contrib_rules_jvm//java:defs.bzl", "java_library", "java_test_suite") 2 | 3 | java_library( 4 | name = "aspect", 5 | srcs = glob(["src/main/**/*.java"]), 6 | visibility = ["//visibility:public"], 7 | deps = [ 8 | "//repository", 9 | "@java_contribs_stable//:org_aspectj_aspectjweaver", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /aspect/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | yoj-aspect 8 | jar 9 | 10 | 11 | tech.ydb.yoj 12 | yoj-parent 13 | 2.6.19-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | YOJ - Transaction Aspect 18 | 19 | Transaction Aspect for YOJ (YDB ORM for Java) implementation. 20 | 21 | 22 | 23 | 24 | tech.ydb.yoj 25 | yoj-repository-ydb-v2 26 | 27 | 28 | org.aspectj 29 | aspectjweaver 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /bom/BUILD: -------------------------------------------------------------------------------- 1 | load("@contrib_rules_jvm//java:defs.bzl", "java_library") 2 | 3 | java_plugin( 4 | name = "lombok_plugin", 5 | generates_api = True, 6 | processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", 7 | deps = ["@java_contribs_stable//:org_projectlombok_lombok"], 8 | ) 9 | 10 | java_library( 11 | name = "lombok", 12 | data = ["lombok.config"], 13 | exported_plugins = [":lombok_plugin"], 14 | neverlink = True, 15 | visibility = ["//visibility:public"], 16 | exports = [ 17 | "@java_contribs_stable//:org_projectlombok_lombok", 18 | "@java_contribs_stable//:org_slf4j_slf4j_api", 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /databind/BUILD: -------------------------------------------------------------------------------- 1 | load("@contrib_rules_jvm//java:defs.bzl", "java_library", "java_test_suite") 2 | 3 | java_library( 4 | name = "databind", 5 | srcs = glob(["src/main/**/*.java"]), 6 | visibility = ["//visibility:public"], 7 | deps = [ 8 | "//bom:lombok", 9 | "//util", 10 | "@java_contribs_stable//:com_google_code_findbugs_jsr305", 11 | "@java_contribs_stable//:com_google_guava_guava", 12 | "@java_contribs_stable//:javax_annotation_javax_annotation_api", 13 | "@java_contribs_stable//:org_jetbrains_annotations", 14 | "@java_contribs_stable//:org_jetbrains_kotlin_kotlin_reflect", 15 | "@java_contribs_stable//:org_jetbrains_kotlin_kotlin_stdlib", 16 | "@java_contribs_stable//:org_slf4j_slf4j_api", 17 | ], 18 | ) 19 | 20 | java_test_suite( 21 | name = "databind-tests", 22 | srcs = glob(["src/test/java/**/*.java"]), 23 | package_prefixes = [".tech"], 24 | resources = glob(["src/test/resources/**"]), 25 | runner = "junit4", 26 | deps = [ 27 | ":databind", 28 | "//bom:lombok", 29 | "@java_contribs_stable//:javax_annotation_javax_annotation_api", 30 | "@java_contribs_stable//:org_assertj_assertj_core", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/DeprecationWarnings.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj; 2 | 3 | import com.google.common.base.Strings; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.Collections; 8 | import java.util.Set; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | @InternalApi 12 | public final class DeprecationWarnings { 13 | private static final Logger log = LoggerFactory.getLogger(DeprecationWarnings.class); 14 | private static final Set warnings = Collections.newSetFromMap(new ConcurrentHashMap<>()); 15 | 16 | private DeprecationWarnings() { 17 | } 18 | 19 | public static void warnOnce(String key, String msg, Object... args) { 20 | if (warnings.add(key)) { 21 | var formattedMsg = Strings.lenientFormat(msg, args); 22 | log.warn(formattedMsg, new Throwable(key + " stack trace")); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/ExperimentalApi.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 7 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 8 | import static java.lang.annotation.ElementType.FIELD; 9 | import static java.lang.annotation.ElementType.METHOD; 10 | import static java.lang.annotation.ElementType.PARAMETER; 11 | import static java.lang.annotation.ElementType.TYPE; 12 | import static java.lang.annotation.RetentionPolicy.SOURCE; 13 | 14 | /** 15 | * Annotates experimental features. These features are not part of the stable YOJ API: they can change incompatibly, 16 | * or even disappear entirely in any release. 17 | */ 18 | @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, ANNOTATION_TYPE}) 19 | @Retention(SOURCE) 20 | public @interface ExperimentalApi { 21 | /** 22 | * @return URL of the GitHub issue tracking the experimental API 23 | */ 24 | String issue(); 25 | } 26 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/InternalApi.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 7 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 8 | import static java.lang.annotation.ElementType.FIELD; 9 | import static java.lang.annotation.ElementType.METHOD; 10 | import static java.lang.annotation.ElementType.MODULE; 11 | import static java.lang.annotation.ElementType.PACKAGE; 12 | import static java.lang.annotation.ElementType.PARAMETER; 13 | import static java.lang.annotation.ElementType.RECORD_COMPONENT; 14 | import static java.lang.annotation.ElementType.TYPE; 15 | import static java.lang.annotation.RetentionPolicy.SOURCE; 16 | 17 | /** 18 | * Annotates internal YOJ implementation details (classes, interfaces, methods, fields, constants, etc.) that need to be 19 | * {@code public} to be used from different and/or multiple packages, but are not stable even across minor YOJ releases. 20 | *

Non-{@code public} (e.g., package-private) classes, interfaces, methods, fields, constants etc. in YOJ are assumed 21 | * to be internal implementation details regardless of the presence of an {@code @InternalApi} annotation on them. 22 | */ 23 | @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE, MODULE, RECORD_COMPONENT}) 24 | @Retention(SOURCE) 25 | public @interface InternalApi { 26 | } 27 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/converter/ObjectColumn.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.converter; 2 | 3 | import tech.ydb.yoj.databind.schema.Column; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.Target; 7 | 8 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 9 | import static java.lang.annotation.ElementType.FIELD; 10 | import static java.lang.annotation.ElementType.RECORD_COMPONENT; 11 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 12 | 13 | /** 14 | * Signifies that the field value is stored in a single database column in an opaque serialized form 15 | * (i.e., individual fields cannot be directly accessed by data binding). 16 | * 17 | * @see tech.ydb.yoj.databind.FieldValueType#OBJECT 18 | */ 19 | @Column(flatten = false) 20 | @Target({FIELD, RECORD_COMPONENT, ANNOTATION_TYPE}) 21 | @Retention(RUNTIME) 22 | public @interface ObjectColumn { 23 | } 24 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/converter/StringColumn.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.converter; 2 | 3 | import tech.ydb.yoj.databind.CustomValueType; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | 6 | import java.lang.annotation.Inherited; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 11 | import static java.lang.annotation.ElementType.FIELD; 12 | import static java.lang.annotation.ElementType.RECORD_COMPONENT; 13 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 14 | 15 | /** 16 | * Annotation for fields that should be stored as text in the database. 17 | *

22 | * 23 | * @see StringValueConverter 24 | */ 25 | @Inherited 26 | @Retention(RUNTIME) 27 | @Target({FIELD, RECORD_COMPONENT, ANNOTATION_TYPE}) 28 | @Column(customValueType = @CustomValueType(columnClass = String.class, converter = StringValueConverter.class)) 29 | public @interface StringColumn { 30 | } 31 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/converter/StringValueType.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.converter; 2 | 3 | import tech.ydb.yoj.databind.CustomValueType; 4 | 5 | import java.lang.annotation.Inherited; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 10 | import static java.lang.annotation.ElementType.TYPE; 11 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 12 | 13 | /** 14 | * Annotation for types that should be stored as text in the database. 15 | * 20 | * 21 | * @see StringValueConverter 22 | */ 23 | @Inherited 24 | @Retention(RUNTIME) 25 | @Target({TYPE, ANNOTATION_TYPE}) 26 | @CustomValueType(columnClass = String.class, converter = StringValueConverter.class) 27 | public @interface StringValueType { 28 | } 29 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/expression/NotExpr.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.expression; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.NonNull; 5 | import lombok.Value; 6 | import tech.ydb.yoj.databind.schema.Schema; 7 | 8 | import java.util.List; 9 | import java.util.function.UnaryOperator; 10 | 11 | import static lombok.AccessLevel.PACKAGE; 12 | import static tech.ydb.yoj.databind.expression.FilterExpression.Type.NOT; 13 | 14 | @Value 15 | @AllArgsConstructor(access = PACKAGE) 16 | public class NotExpr implements FilterExpression { 17 | @NonNull 18 | Schema schema; 19 | 20 | @NonNull 21 | FilterExpression delegate; 22 | 23 | @Override 24 | public Type getType() { 25 | return NOT; 26 | } 27 | 28 | @Override 29 | public V visit(@NonNull Visitor visitor) { 30 | return visitor.visitNotExpr(this); 31 | } 32 | 33 | @Override 34 | public FilterExpression negate() { 35 | return delegate; 36 | } 37 | 38 | @Override 39 | public NotExpr forSchema(@NonNull Schema dstSchema, @NonNull UnaryOperator pathTransformer) { 40 | return new NotExpr<>(dstSchema, this.delegate.forSchema(dstSchema, pathTransformer)); 41 | } 42 | 43 | @Override 44 | public List> getChildren() { 45 | return List.of(delegate); 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "NOT (" + delegate + ")"; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/expression/values/BooleanFieldValue.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.expression.values; 2 | 3 | import tech.ydb.yoj.databind.FieldValueType; 4 | import tech.ydb.yoj.databind.expression.IllegalExpressionException.FieldTypeError.BooleanFieldExpected; 5 | 6 | import java.lang.reflect.Type; 7 | import java.util.Optional; 8 | 9 | import static java.lang.String.format; 10 | import static tech.ydb.yoj.databind.expression.values.FieldValue.ValidationResult.invalidFieldValue; 11 | import static tech.ydb.yoj.databind.expression.values.FieldValue.ValidationResult.validFieldValue; 12 | 13 | public record BooleanFieldValue(boolean bool) implements FieldValue { 14 | @Override 15 | public Optional> getComparableByType(Type fieldType, FieldValueType valueType) { 16 | if (valueType != FieldValueType.BOOLEAN) { 17 | return Optional.empty(); 18 | } 19 | 20 | return Optional.of(bool); 21 | } 22 | 23 | @Override 24 | public ValidationResult isValidValueOfType(Type fieldType, FieldValueType valueType) { 25 | return valueType == FieldValueType.BOOLEAN 26 | ? validFieldValue() 27 | : invalidFieldValue(BooleanFieldExpected::new, p -> format("Specified a boolean value for non-boolean field \"%s\"", p)); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return Boolean.toString(bool); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/expression/values/ByteArrayFieldValue.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.expression.values; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.databind.ByteArray; 5 | import tech.ydb.yoj.databind.FieldValueType; 6 | import tech.ydb.yoj.databind.expression.IllegalExpressionException.FieldTypeError.ByteArrayFieldExpected; 7 | 8 | import java.lang.reflect.Type; 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | 12 | import static java.lang.String.format; 13 | import static tech.ydb.yoj.databind.expression.values.FieldValue.ValidationResult.invalidFieldValue; 14 | import static tech.ydb.yoj.databind.expression.values.FieldValue.ValidationResult.validFieldValue; 15 | 16 | public record ByteArrayFieldValue(@NonNull ByteArray byteArray) implements FieldValue { 17 | @Override 18 | public Optional> getComparableByType(Type fieldType, FieldValueType valueType) { 19 | if (Objects.requireNonNull(valueType) != FieldValueType.BYTE_ARRAY) { 20 | return Optional.empty(); 21 | } 22 | 23 | return Optional.of(byteArray); 24 | } 25 | 26 | @Override 27 | public ValidationResult isValidValueOfType(Type fieldType, FieldValueType valueType) { 28 | return valueType == FieldValueType.BYTE_ARRAY 29 | ? validFieldValue() 30 | : invalidFieldValue(ByteArrayFieldExpected::new, p -> format("Specified a ByteArray value for non-ByteArray field \"%s\"", p)); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return byteArray.toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/expression/values/UuidFieldValue.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.expression.values; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.databind.FieldValueType; 5 | import tech.ydb.yoj.databind.expression.IllegalExpressionException.FieldTypeError.UuidFieldExpected; 6 | 7 | import java.lang.reflect.Type; 8 | import java.util.Optional; 9 | import java.util.UUID; 10 | 11 | import static java.lang.String.format; 12 | import static tech.ydb.yoj.databind.expression.values.FieldValue.ValidationResult.invalidFieldValue; 13 | import static tech.ydb.yoj.databind.expression.values.FieldValue.ValidationResult.validFieldValue; 14 | 15 | public record UuidFieldValue(@NonNull UUID uuid) implements FieldValue { 16 | @Override 17 | public Optional> getComparableByType(Type fieldType, FieldValueType valueType) { 18 | return switch (valueType) { 19 | case UUID, STRING -> Optional.of(uuid.toString()); 20 | default -> Optional.empty(); 21 | }; 22 | } 23 | 24 | @Override 25 | public ValidationResult isValidValueOfType(Type fieldType, FieldValueType valueType) { 26 | return valueType == FieldValueType.UUID || valueType == FieldValueType.STRING 27 | ? validFieldValue() // All UUIDs are representable as both UUID and String 28 | : invalidFieldValue(UuidFieldExpected::new, p -> format("Specified an UUID value for non-UUID/non-String field \"%s\"", p)); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return uuid.toString(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/expression/visitor/AllMatch.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.expression.visitor; 2 | 3 | import lombok.NonNull; 4 | import lombok.RequiredArgsConstructor; 5 | import tech.ydb.yoj.databind.expression.FilterExpression; 6 | import tech.ydb.yoj.databind.expression.LeafExpression; 7 | 8 | import java.util.function.Predicate; 9 | 10 | @RequiredArgsConstructor 11 | public final class AllMatch extends FilterExpression.Visitor.Simple { 12 | @NonNull 13 | private final Predicate> predicate; 14 | 15 | @Override 16 | public Boolean visitLeaf(@NonNull LeafExpression leaf) { 17 | return predicate.test(leaf); 18 | } 19 | 20 | @Override 21 | protected Boolean visitComposite(@NonNull FilterExpression composite) { 22 | return composite.stream().allMatch(e -> e.visit(this)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/expression/visitor/AnyMatch.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.expression.visitor; 2 | 3 | import lombok.NonNull; 4 | import lombok.RequiredArgsConstructor; 5 | import tech.ydb.yoj.databind.expression.FilterExpression; 6 | import tech.ydb.yoj.databind.expression.LeafExpression; 7 | 8 | import java.util.function.Predicate; 9 | 10 | @RequiredArgsConstructor 11 | public final class AnyMatch extends FilterExpression.Visitor.Simple { 12 | @NonNull 13 | private final Predicate> predicate; 14 | 15 | @Override 16 | public Boolean visitLeaf(@NonNull LeafExpression leaf) { 17 | return predicate.test(leaf); 18 | } 19 | 20 | @Override 21 | protected Boolean visitComposite(@NonNull FilterExpression composite) { 22 | return composite.stream().anyMatch(e -> e.visit(this)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/expression/visitor/RemoveIf.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.expression.visitor; 2 | 3 | import lombok.NonNull; 4 | import lombok.RequiredArgsConstructor; 5 | import tech.ydb.yoj.databind.expression.FilterExpression; 6 | import tech.ydb.yoj.databind.expression.LeafExpression; 7 | 8 | import java.util.List; 9 | import java.util.Objects; 10 | import java.util.function.Predicate; 11 | 12 | import static java.util.stream.Collectors.toList; 13 | import static tech.ydb.yoj.databind.expression.FilterBuilder.and; 14 | import static tech.ydb.yoj.databind.expression.FilterBuilder.not; 15 | import static tech.ydb.yoj.databind.expression.FilterBuilder.or; 16 | 17 | @RequiredArgsConstructor 18 | public final class RemoveIf extends FilterExpression.Visitor.Simple> { 19 | @NonNull 20 | private final Predicate> predicate; 21 | 22 | @Override 23 | protected FilterExpression visitLeaf(@NonNull LeafExpression leaf) { 24 | return predicate.test(leaf) ? null : leaf; 25 | } 26 | 27 | @Override 28 | protected FilterExpression visitComposite(@NonNull FilterExpression composite) { 29 | List> filtered = composite.stream() 30 | .map(e -> e.visit(this)) 31 | .filter(Objects::nonNull) 32 | .collect(toList()); 33 | if (filtered.isEmpty()) { 34 | return null; 35 | } 36 | 37 | return switch (composite.getType()) { 38 | case OR -> or(filtered); 39 | case AND -> and(filtered); 40 | case NOT -> not(filtered.get(0)); 41 | default -> throw new UnsupportedOperationException("Unknown composite expression:" + composite); 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/BindingException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import lombok.NonNull; 4 | 5 | import javax.annotation.Nullable; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.util.function.Function; 8 | 9 | public abstract class BindingException extends IllegalArgumentException { 10 | protected BindingException(@Nullable Throwable cause, @NonNull Function msgFunc) { 11 | super(msgFunc.apply(rootCause(cause)), rootCause(cause)); 12 | } 13 | 14 | private static Throwable rootCause(Throwable cause) { 15 | return cause instanceof InvocationTargetException ? cause.getCause() : cause; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/Changefeeds.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.TYPE; 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | @Target(TYPE) 10 | @Retention(RUNTIME) 11 | public @interface Changefeeds { 12 | Changefeed[] value(); 13 | } 14 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/ConstructionException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import lombok.NonNull; 4 | 5 | import javax.annotation.Nullable; 6 | import java.lang.reflect.Constructor; 7 | import java.util.Arrays; 8 | import java.util.Optional; 9 | import java.util.regex.Pattern; 10 | 11 | import static java.lang.String.format; 12 | import static java.util.stream.Collectors.joining; 13 | 14 | /** 15 | * Could not construct object according to {@link Schema} with the specified field value. 16 | */ 17 | public final class ConstructionException extends BindingException { 18 | public ConstructionException(@NonNull Constructor ctor, @NonNull Object[] args, @Nullable Throwable cause) { 19 | super(cause, ex -> message(ctor, args, ex)); 20 | } 21 | 22 | private static String message(Constructor ctor, Object[] args, Throwable ex) { 23 | Class clazz = ctor.getDeclaringClass(); 24 | String shortClassName = Optional.ofNullable(clazz.getCanonicalName()) 25 | .map(n -> n.replaceFirst("^" + Pattern.quote(clazz.getPackageName()) + "\\.", "")) 26 | .orElse("???"); 27 | String argString = Arrays.stream(args).map(String::valueOf).collect(joining(", ")); 28 | return format("Could not construct new %s(%s)%s", shortClassName, argString, ex == null ? "" : ": " + ex); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/CustomConverterException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | import tech.ydb.yoj.ExperimentalApi; 5 | 6 | @ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/24") 7 | public final class CustomConverterException extends BindingException { 8 | public CustomConverterException(@Nullable Throwable cause, String message) { 9 | super(cause, __ -> message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/CustomValueTypeInfo.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import lombok.NonNull; 4 | import lombok.Value; 5 | import tech.ydb.yoj.ExperimentalApi; 6 | import tech.ydb.yoj.databind.converter.ValueConverter; 7 | import tech.ydb.yoj.databind.schema.Schema.JavaField; 8 | 9 | @Value 10 | @ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/24") 11 | public class CustomValueTypeInfo> { 12 | @NonNull 13 | Class columnClass; 14 | 15 | @NonNull 16 | ValueConverter converter; 17 | 18 | @NonNull 19 | public J toJava(@NonNull JavaField field, @NonNull C column) { 20 | return converter.toJava(field, column); 21 | } 22 | 23 | @NonNull 24 | public C toColumn(@NonNull JavaField field, @NonNull J java) { 25 | return converter.toColumn(field, java); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/FieldValueException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import static java.lang.String.format; 6 | 7 | public final class FieldValueException extends BindingException { 8 | public FieldValueException(@Nullable Throwable cause, String fieldName, Object containingObject) { 9 | super(cause, ex -> message(fieldName, containingObject, ex)); 10 | } 11 | 12 | private static String message(String fieldName, Object obj, Throwable ex) { 13 | String safeObj; 14 | try { 15 | safeObj = String.valueOf(obj); 16 | } catch (Exception toStrEx) { 17 | safeObj = format("[value of %s; threw on toString(): %s]", obj.getClass(), toStrEx); 18 | } 19 | 20 | return format("Could not get value of field \"%s\" from %s%s", fieldName, safeObj, ex == null ? "" : ": " + ex); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/GlobalIndex.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import java.lang.annotation.Repeatable; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.TYPE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Specifies the index for the annotated entity. 12 | * 13 | *
14 |  *    Example:
15 |  *
16 |  *    @GlobalIndex(name="views_index", fields = {"name", "type"})
17 |  *    public class Customer { ... }
18 |  * 
19 | */ 20 | @Target(TYPE) 21 | @Retention(RUNTIME) 22 | @Repeatable(GlobalIndexes.class) 23 | public @interface GlobalIndex { 24 | /** 25 | * Index name. 26 | */ 27 | String name(); 28 | 29 | /** 30 | * List of annotated class fields representing index columns. 31 | */ 32 | String[] fields(); 33 | 34 | /** 35 | * Index type 36 | */ 37 | Type type() default Type.GLOBAL; 38 | 39 | enum Type { 40 | GLOBAL, 41 | UNIQUE 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/GlobalIndexes.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.TYPE; 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | @Target(TYPE) 10 | @Retention(RUNTIME) 11 | public @interface GlobalIndexes { 12 | GlobalIndex[] value(); 13 | } 14 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/ObjectSchema.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import tech.ydb.yoj.databind.schema.configuration.SchemaRegistry; 4 | import tech.ydb.yoj.databind.schema.configuration.SchemaRegistry.SchemaKey; 5 | import tech.ydb.yoj.databind.schema.reflect.Reflector; 6 | 7 | public final class ObjectSchema extends Schema { 8 | private ObjectSchema(SchemaKey key, Reflector reflector) { 9 | super(key, reflector); 10 | } 11 | 12 | public static ObjectSchema of(Class type) { 13 | return of(SchemaRegistry.getDefault(), type); 14 | } 15 | 16 | public static ObjectSchema of(SchemaRegistry registry, Class type) { 17 | return registry.getOrCreate(ObjectSchema.class, ObjectSchema::new, SchemaKey.of(type)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/TTL.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Specifies TTL settings fot the annotated entity. 10 | * 11 | *
12 |  *      Example:
13 |  *
14 |  *      @TTL(field = "createdAt", interval = "PT12H")
15 |  *      public class LogEntry { ... }
16 |  * 
17 | */ 18 | @Target(ElementType.TYPE) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | public @interface TTL { 21 | /** 22 | * Field, which will be used to calculate if the row might be deleted 23 | * Accepted dbTypes of columns: 24 | *
    25 | *
  • Date
  • 26 | *
  • Datetime
  • 27 | *
  • Timestamp
  • 28 | *
29 | */ 30 | String field(); 31 | 32 | /** 33 | * Interval in ISO 8601 format defining lifespan of the row 34 | */ 35 | String interval(); 36 | } 37 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/Table.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.TYPE; 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | /** 10 | * Specifies the table for the annotated entity. 11 | * 12 | * If no {@code Table} annotation is specified, the default values apply. 13 | * 14 | *
15 |  *    Example:
16 |  *
17 |  *    @Table(name="CUSTOMERS")
18 |  *    public class Customer { ... }
19 |  * 
20 | * 21 | */ 22 | @Target(TYPE) 23 | @Retention(RUNTIME) 24 | public @interface Table { 25 | 26 | /** 27 | * The name of the table. Defaults to the type name. 28 | */ 29 | String name(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/naming/DelegateNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.naming; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.experimental.Delegate; 6 | 7 | @AllArgsConstructor 8 | @EqualsAndHashCode 9 | public class DelegateNamingStrategy implements NamingStrategy { 10 | @Delegate 11 | protected final NamingStrategy delegate; 12 | } 13 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/naming/NamingStrategy.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.naming; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.databind.schema.Schema.JavaField; 5 | 6 | public interface NamingStrategy { 7 | String NAME_DELIMITER = "_"; 8 | 9 | String getNameForClass(@NonNull Class entityClass); 10 | 11 | /** 12 | * Assigns a name to a field in a schema. 13 | */ 14 | void assignFieldName(@NonNull JavaField javaField); 15 | } 16 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/KotlinReflectionDetector.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.reflect; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /*package*/ final class KotlinReflectionDetector { 7 | private static final Logger log = LoggerFactory.getLogger(KotlinReflectionDetector.class); 8 | 9 | private KotlinReflectionDetector() { 10 | } 11 | 12 | public static final boolean kotlinAvailable = detectKotlinReflection(); 13 | 14 | private static boolean detectKotlinReflection() { 15 | var cl = classLoader(); 16 | 17 | try { 18 | Class.forName("kotlin.Metadata", false, cl); 19 | } catch (ClassNotFoundException e) { 20 | return false; 21 | } 22 | 23 | try { 24 | Class.forName("kotlin.reflect.full.KClasses", false, cl); 25 | return true; 26 | } catch (ClassNotFoundException e) { 27 | log.warn("YOJ has detected Kotlin but not kotlin-reflect. Kotlin data classes won't work as Entities."); 28 | return false; 29 | } 30 | } 31 | 32 | private static ClassLoader classLoader() { 33 | ClassLoader cl = null; 34 | try { 35 | cl = Thread.currentThread().getContextClassLoader(); 36 | } catch (Exception ignore) { 37 | } 38 | if (cl == null) { 39 | cl = KotlinDataClassType.class.getClassLoader(); 40 | if (cl == null) { 41 | try { 42 | cl = ClassLoader.getSystemClassLoader(); 43 | } catch (Exception ignore) { 44 | } 45 | } 46 | } 47 | return cl; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/PojoField.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.reflect; 2 | 3 | import com.google.common.base.Preconditions; 4 | import lombok.NonNull; 5 | import tech.ydb.yoj.databind.schema.FieldValueException; 6 | 7 | import javax.annotation.Nullable; 8 | 9 | /** 10 | * Represents a field of a POJO class, hand-written or generated e.g. by Lombok. 11 | */ 12 | /*package*/ final class PojoField extends ReflectFieldBase { 13 | private final java.lang.reflect.Field delegate; 14 | 15 | public PojoField(@NonNull Reflector reflector, @NonNull java.lang.reflect.Field delegate) { 16 | super(reflector, delegate.getName(), delegate.getGenericType(), delegate.getType(), delegate); 17 | 18 | Preconditions.checkArgument(!delegate.isSynthetic(), 19 | "Encountered a synthetic field, did you forget to declare the ID class as static? Field is: %s", delegate); 20 | this.delegate = delegate; 21 | this.delegate.setAccessible(true); 22 | } 23 | 24 | @Nullable 25 | @Override 26 | public Object getValue(Object containingObject) { 27 | try { 28 | return delegate.get(containingObject); 29 | } catch (Exception e) { 30 | throw new FieldValueException(e, getName(), containingObject); 31 | } 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "PojoField[" + getGenericType().getTypeName() + "::" + getName() + "]"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/RecordField.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.reflect; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.databind.schema.FieldValueException; 5 | 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * Represents a record class component for the purposes of YOJ data-binding. 10 | */ 11 | /*package*/ final class RecordField extends ReflectFieldBase { 12 | private final java.lang.reflect.Method accessor; 13 | 14 | public RecordField(@NonNull Reflector reflector, @NonNull java.lang.reflect.RecordComponent delegate) { 15 | super(reflector, delegate.getName(), delegate.getGenericType(), delegate.getType(), delegate); 16 | 17 | this.accessor = delegate.getAccessor(); 18 | accessor.setAccessible(true); 19 | } 20 | 21 | @Nullable 22 | @Override 23 | public Object getValue(Object containingObject) { 24 | try { 25 | return accessor.invoke(containingObject); 26 | } catch (Exception e) { 27 | throw new FieldValueException(e, getName(), containingObject); 28 | } 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "RecordField[" + getGenericType().getTypeName() + "::" + getName() + "]"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/ReflectFieldBase.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.reflect; 2 | 3 | import lombok.Getter; 4 | import tech.ydb.yoj.databind.CustomValueTypes; 5 | import tech.ydb.yoj.databind.FieldValueType; 6 | import tech.ydb.yoj.databind.schema.Column; 7 | import tech.ydb.yoj.databind.schema.CustomValueTypeInfo; 8 | import tech.ydb.yoj.util.lang.Annotations; 9 | 10 | import java.lang.reflect.AnnotatedElement; 11 | import java.lang.reflect.Type; 12 | 13 | @Getter 14 | public abstract class ReflectFieldBase implements ReflectField { 15 | private final String name; 16 | 17 | private final Type genericType; 18 | private final Class type; 19 | 20 | private final Column column; 21 | private final CustomValueTypeInfo customValueTypeInfo; 22 | 23 | private final FieldValueType valueType; 24 | 25 | private final ReflectType reflectType; 26 | 27 | protected ReflectFieldBase(Reflector reflector, 28 | String name, 29 | Type genericType, Class type, 30 | AnnotatedElement component) { 31 | this.name = name; 32 | this.genericType = genericType; 33 | this.type = type; 34 | this.column = Annotations.find(Column.class, component); 35 | this.customValueTypeInfo = CustomValueTypes.getCustomValueTypeInfo(genericType, column); 36 | this.valueType = FieldValueType.forJavaType(genericType, column, customValueTypeInfo); 37 | this.reflectType = reflector.reflectFieldType(genericType, valueType); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/ReflectType.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.reflect; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.util.List; 5 | 6 | /** 7 | * Basic reflection information for the specified raw type. 8 | * 9 | * @param raw type 10 | */ 11 | public interface ReflectType { 12 | /** 13 | * Returns subfields of this type in the order of their appearance in {@link #getConstructor() the canonical all-arg 14 | * constructor}. 15 | * 16 | * @return list of subfields of this type in constructor order; might be empty 17 | */ 18 | List getFields(); 19 | 20 | /** 21 | * @return canonical all-args constructor for this type 22 | * @throws UnsupportedOperationException if this type cannot be constructed from a list of its field values 23 | */ 24 | Constructor getConstructor(); 25 | 26 | /** 27 | * @return raw type that this reflection information describes 28 | */ 29 | Class getRawType(); 30 | } 31 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/Reflector.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.reflect; 2 | 3 | import tech.ydb.yoj.databind.FieldValueType; 4 | 5 | import java.lang.reflect.Type; 6 | 7 | /** 8 | * Common interface for slightly different reflection logic (for Java POJOs, Java records, Kotlin data classes etc.) 9 | * used in YOJ data-binding to build a {@link tech.ydb.yoj.databind.schema.Schema schema} with 10 | * {@link tech.ydb.yoj.databind.schema.Schema.JavaField fields}. 11 | */ 12 | public interface Reflector { 13 | /** 14 | * Gets reflection information for a root type, that is, a type that can have a 15 | * {@link tech.ydb.yoj.databind.schema.Schema schema}: a public concrete class. 16 | * 17 | * @param type type to get reflection information for 18 | * @param type to get reflection information for 19 | * @return basic reflection information for {@code type} 20 | */ 21 | ReflectType reflectRootType(Class type); 22 | 23 | /** 24 | * Gets reflection information for a field, potentially a deep sub-field of some type which can have schema. 25 | * 26 | * @param genericType generic type of field to get reflection information for 27 | * @param bindingType value type suitable for data-binding of type {@code genericType} 28 | * @return basic reflection information for the field 29 | */ 30 | ReflectType reflectFieldType(Type genericType, FieldValueType bindingType); 31 | } 32 | -------------------------------------------------------------------------------- /databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/SimpleType.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.reflect; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.databind.FieldValueType; 5 | 6 | import java.lang.reflect.Constructor; 7 | import java.util.List; 8 | 9 | /** 10 | * Represents a simple data type which has no subfields and even no explicit constructor, e.g. an {@code int}. 11 | * 12 | * @param simple type 13 | */ 14 | /*package*/ final class SimpleType implements ReflectType { 15 | public static final StdReflector.TypeFactory FACTORY = new StdReflector.TypeFactory() { 16 | @Override 17 | public int priority() { 18 | return 0; 19 | } 20 | 21 | @Override 22 | public boolean matches(Class rawType, FieldValueType fvt) { 23 | return !fvt.isComposite(); 24 | } 25 | 26 | @Override 27 | public ReflectType create(Reflector reflector, Class rawType, FieldValueType fvt) { 28 | return new SimpleType<>(rawType); 29 | } 30 | }; 31 | 32 | private final Class type; 33 | 34 | public SimpleType(@NonNull Class type) { 35 | this.type = type; 36 | } 37 | 38 | @Override 39 | public List getFields() { 40 | return List.of(); 41 | } 42 | 43 | @Override 44 | public Constructor getConstructor() { 45 | throw new UnsupportedOperationException("SimpleType.getConstructor(): trying to instantiate " + type); 46 | } 47 | 48 | @Override 49 | public Class getRawType() { 50 | return type; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "SimpleType[" + type + "]"; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /databind/src/test/java/tech/ydb/yoj/databind/schema/naming/AnnotatedEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.naming; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | import tech.ydb.yoj.databind.schema.Table; 6 | 7 | @Table(name = "annotated$entities") 8 | @Value 9 | public class AnnotatedEntity { 10 | Id id; 11 | 12 | @Column(name = "column_name") 13 | String field; 14 | 15 | @Value 16 | private static class Id { 17 | 18 | @Column(name = "str$val") 19 | String stringValue; 20 | 21 | @Column(name = "int.val") 22 | Integer intValue; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /databind/src/test/java/tech/ydb/yoj/databind/schema/naming/BaseNamingStrategyTestBase.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.naming; 2 | 3 | import tech.ydb.yoj.databind.schema.Schema; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public abstract class BaseNamingStrategyTestBase { 8 | protected abstract Schema getSchema(Class entityType); 9 | 10 | protected void verifyTableName(Class entityType, String tableName) { 11 | Schema schema = getSchema(entityType); 12 | 13 | assertThat(schema.getName()).isEqualTo(tableName); 14 | } 15 | 16 | protected void verifyFieldNames(Class entityType, String... names) { 17 | Schema schema = getSchema(entityType); 18 | 19 | assertThat(schema.flattenFieldNames()).containsOnly(names); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /databind/src/test/java/tech/ydb/yoj/databind/schema/naming/MixedEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.naming; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | 6 | @Value 7 | public class MixedEntity { 8 | Id id; 9 | 10 | @Column(name = "column_name") 11 | String field; 12 | 13 | TestEntity.SubEntity subEntity; 14 | 15 | SubEntityWithRelative relativeWithoutColumnAnnotation; 16 | 17 | @Column(name = "prefix") 18 | SubEntityWithRelative relativeWithPrefix; 19 | 20 | @Value 21 | private static class Id { 22 | String stringValue; 23 | 24 | @Column(name = "int.val") 25 | Integer intValue; 26 | } 27 | 28 | @Value 29 | static class SubEntityWithRelative { 30 | @Column(name = "sfe_relative", columnNaming = Column.ColumnNaming.RELATIVE) 31 | SingleFieldEntity singleFieldEntityRelative; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /databind/src/test/java/tech/ydb/yoj/databind/schema/naming/SingleFieldEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.naming; 2 | 3 | import lombok.Value; 4 | 5 | import java.time.Instant; 6 | 7 | @Value 8 | public class SingleFieldEntity { 9 | Instant timestamp; 10 | } 11 | -------------------------------------------------------------------------------- /databind/src/test/java/tech/ydb/yoj/databind/schema/naming/TestEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.databind.schema.naming; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | 6 | @Value 7 | public class TestEntity { 8 | String field; 9 | Id id; 10 | 11 | @Value 12 | private static class Id { 13 | String stringValue; 14 | Integer intValue; 15 | } 16 | 17 | @Value 18 | static class SubEntity { 19 | boolean boolValue; 20 | 21 | @Column(columnNaming = Column.ColumnNaming.ABSOLUTE) 22 | boolean absoluteBoolValue; 23 | 24 | @Column(name = "sfe") 25 | SingleFieldEntity singleFieldEntity; 26 | 27 | @Column(name = "sfe_absolute", columnNaming = Column.ColumnNaming.ABSOLUTE) 28 | SingleFieldEntity singleFieldEntityAbsolute; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /databind/src/test/resources/log4j2.yaml: -------------------------------------------------------------------------------- 1 | Configuration: 2 | appenders: 3 | Console: 4 | name: stdout 5 | PatternLayout: 6 | Pattern: "%d %-5level %-6X{tx} [%t] %c{1.}: %msg%n%throwable" 7 | 8 | Loggers: 9 | Root: 10 | level: info 11 | AppenderRef: 12 | ref: stdout 13 | Logger: 14 | - name: tech.ydb.yoj.databind 15 | level: debug 16 | -------------------------------------------------------------------------------- /ext-meta-generator/src/main/java/tech/ydb/yoj/generator/Utils.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.generator; 2 | 3 | import com.google.common.base.Strings; 4 | 5 | public final class Utils { 6 | private Utils() { 7 | } 8 | 9 | public static String concatFieldNameChain(String one, String two) { 10 | if (Strings.isNullOrEmpty(one)) { 11 | return two; 12 | } 13 | if (Strings.isNullOrEmpty(two)) { 14 | return one; 15 | } 16 | return one + "." + two; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ext-meta-generator/src/main/resources/log4j2.yaml: -------------------------------------------------------------------------------- 1 | Configuration: 2 | appenders: 3 | Console: 4 | name: stdout 5 | PatternLayout: 6 | Pattern: "%d %-5level %-6X{tx} [%t] %c{1.}: %msg%n%throwable" 7 | Loggers: 8 | Root: 9 | level: info 10 | -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/input/ComplexNesting.java: -------------------------------------------------------------------------------- 1 | package input; 2 | 3 | import tech.ydb.yoj.databind.schema.Table; 4 | 5 | @Table(name = "table") 6 | public class ComplexNesting { 7 | 8 | String field; 9 | // We 'jump over' Class1_2 to see if it's available and produce a correct result 10 | Class1.Class1_2.Class1_2_3 complex; 11 | Class1.Class1_2.EmptyClass emptyField; 12 | 13 | static class Class1 { 14 | Object someField; // Must ignore 15 | Class1_2 class1Field; // Must Ignore 16 | 17 | static class Class1_2 { 18 | Object class2Field; 19 | EmptyClass emptyField; 20 | 21 | static class Class1_2_3 { 22 | Object class123Field; 23 | // important case: despite EmptyClass being defined outside of this class 24 | // it still available and must be treated as complex 25 | EmptyClass emptyField; 26 | } 27 | 28 | static class EmptyClass { 29 | 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/input/EntityWithComplexSingularId.java: -------------------------------------------------------------------------------- 1 | package input; 2 | 3 | import java.time.Instant; 4 | 5 | import tech.ydb.yoj.databind.schema.Column; 6 | import tech.ydb.yoj.databind.schema.Table; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | 9 | import javax.annotation.Nonnull; 10 | import javax.annotation.Nullable; 11 | 12 | @Table(name = "audit_event_record") 13 | public class EntityWithComplexSingularId implements Entity { 14 | 15 | @Column 16 | @Nonnull 17 | private Id id; 18 | 19 | @Override 20 | public Entity.Id getId() { 21 | return id; 22 | } 23 | 24 | public static class Id implements Entity.Id { 25 | 26 | @Nonnull 27 | private NestedId value; 28 | 29 | private static class NestedId { 30 | @Nonnull 31 | private MoreNestedId value; 32 | private EmptyField emptyField; 33 | 34 | private static class EmptyField { 35 | 36 | } 37 | } 38 | 39 | private static class MoreNestedId { 40 | @Nonnull 41 | private String value; 42 | } 43 | } 44 | 45 | private NotId notId; 46 | private static class NotId { 47 | NestedNotId nestedNotId; 48 | private static class NestedNotId{ 49 | @Nonnull 50 | private final String value = ""; 51 | } 52 | } 53 | 54 | @Nullable 55 | private Instant lastUpdated; 56 | 57 | @Nullable 58 | private Object payload; 59 | } 60 | -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/input/EntityWithSimpleSingularId.java: -------------------------------------------------------------------------------- 1 | package input; 2 | 3 | import java.time.Instant; 4 | 5 | import tech.ydb.yoj.databind.schema.Column; 6 | import tech.ydb.yoj.databind.schema.Table; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | 9 | import javax.annotation.Nonnull; 10 | import javax.annotation.Nullable; 11 | 12 | @Table(name = "audit_event_record") 13 | public class EntityWithSimpleSingularId implements Entity { 14 | 15 | @Column 16 | @Nonnull 17 | private Id id; 18 | 19 | @Override 20 | public Entity.Id getId() { 21 | return id; 22 | } 23 | 24 | public static class Id implements Entity.Id { 25 | 26 | @Nonnull 27 | private final String value = ""; 28 | } 29 | 30 | private NotId notId; 31 | private static class NotId { 32 | @Nonnull 33 | private final String value = ""; 34 | } 35 | 36 | @Nullable 37 | private final Instant lastUpdated = Instant.now(); 38 | 39 | @Nullable 40 | private Object payload; 41 | } -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/input/KotlinDataClass.kt: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import tech.ydb.yoj.databind.schema.Column 4 | import tech.ydb.yoj.databind.schema.Table 5 | import tech.ydb.yoj.repository.db.Entity 6 | 7 | @Table(name = "my_table") 8 | data class KotlinDataClass( 9 | @Column(name = "id") 10 | private val id: Id, 11 | 12 | @Column(name = "cloud_id") 13 | val field1: String, 14 | 15 | @Column(name = "folder_id") 16 | val field2: String, 17 | 18 | @Column(name = "status") 19 | val status: SomeEnum, 20 | 21 | val data: NotNestedClass, 22 | 23 | ) : Entity { 24 | override fun getId() = id 25 | 26 | data class Id( 27 | val nestedField1: String, 28 | val nestedField2: String 29 | ) : Entity.Id 30 | } 31 | 32 | data class NotNestedClass( 33 | @Column(name = "issued_at") 34 | val issuedAt: Long, 35 | @Column(name = "algorithm") 36 | val algorithm: String? 37 | ) 38 | 39 | enum class SomeEnum { 40 | STATUS_UNSPECIFIED, 41 | UNSIGNED, 42 | ACTIVE 43 | } -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/input/NestedClass.java: -------------------------------------------------------------------------------- 1 | package package_name; 2 | 3 | import tech.ydb.yoj.databind.schema.Table; 4 | 5 | @Table(name = "audit_event_record") 6 | public class NestedClass { 7 | 8 | String field; 9 | // Check that every complex field will generate its own nested class hierarchy 10 | // (See output/NestedClassFields.java) 11 | A a1; 12 | A a2; 13 | C c; 14 | 15 | 16 | static class A { 17 | String fieldA; 18 | 19 | B b1; 20 | B b2; 21 | 22 | static class B { 23 | String fieldB1; 24 | String fieldB2; 25 | } 26 | } 27 | 28 | static class C { 29 | D d; // Class D is not nested inside C but still available 30 | } 31 | 32 | static class D { 33 | String fieldD; 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/input/NoSimpleFieldsClass.java: -------------------------------------------------------------------------------- 1 | package some_package; 2 | 3 | import tech.ydb.yoj.databind.schema.Table; 4 | 5 | @Table(name = "table") 6 | class NoSimpleFieldsClass { 7 | 8 | Class1 field01; 9 | Class1 field02; 10 | 11 | private static class Class1 { 12 | Class2 field11; 13 | Class2.Class3 field12; 14 | 15 | private static class IgnoreMe1 { 16 | String field; 17 | } 18 | 19 | private class Class2 { 20 | Class3 field2; 21 | 22 | private static class Class3 { 23 | 24 | } 25 | } 26 | } 27 | 28 | private static class IgnoreMe0 { 29 | String field; 30 | } 31 | } -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/input/TestRecord.java: -------------------------------------------------------------------------------- 1 | //no package; 2 | 3 | import tech.ydb.yoj.databind.schema.Table; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | 6 | @Table(name = "table") 7 | record TestRecord(String fieldOne, String field, Id id, InnerClass ic) implements Entity { 8 | 9 | @Override 10 | public Entity.Id getId() { 11 | return id; 12 | } 13 | 14 | record Id(String value) implements Entity.Id { 15 | } 16 | 17 | static class InnerClass { 18 | String innerClassValue; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/input/UnconventionalFieldNames.java: -------------------------------------------------------------------------------- 1 | package input; 2 | 3 | import tech.ydb.yoj.databind.schema.Table; 4 | 5 | @Table(name = "table") 6 | class UnconventionalFieldNames { 7 | 8 | //Trying to trick generator to generate the same name of constant 9 | String fieldOne; 10 | String fIeldOne; 11 | String f_ieldTwo; 12 | String f_IeldTwo; 13 | 14 | 15 | // Just some unconventional names to check generated source 16 | String _fie_ldWithUnderscores; 17 | strange_Name FieldIn_UpperCamelCase; 18 | strange_Name _FieldStartedWithUnderScore; 19 | 20 | class strange_Name { 21 | Object NestedFieldInUpperCamelCase; 22 | } 23 | } -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/output/ComplexNestingFields.java: -------------------------------------------------------------------------------- 1 | package input.generated; 2 | 3 | import javax.annotation.processing.Generated; 4 | 5 | @Generated("tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor") 6 | public final class ComplexNestingFields { 7 | private ComplexNestingFields(){} 8 | 9 | public static final String FIELD = "field"; 10 | public static final String COMPLEX_OBJ = "complex"; 11 | public static final class Complex { 12 | private Complex(){} 13 | 14 | public static final String CLASS123_FIELD = "complex.class123Field"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/output/EntityWithComplexSingularIdFields.java: -------------------------------------------------------------------------------- 1 | package input.generated; 2 | 3 | import javax.annotation.processing.Generated; 4 | 5 | @Generated("tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor") 6 | public final class EntityWithComplexSingularIdFields { 7 | private EntityWithComplexSingularIdFields(){} 8 | 9 | public static final String ID = "id"; 10 | public static final String LAST_UPDATED = "lastUpdated"; 11 | public static final String PAYLOAD = "payload"; 12 | public static final String NOT_ID_OBJ = "notId"; 13 | public static final class NotId { 14 | private NotId(){} 15 | 16 | public static final String NESTED_NOT_ID_OBJ = "notId.nestedNotId"; 17 | public static final class NestedNotId { 18 | private NestedNotId(){} 19 | 20 | public static final String VALUE = "notId.nestedNotId.value"; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/output/EntityWithSimpleSingularIdFields.java: -------------------------------------------------------------------------------- 1 | package input.generated; 2 | 3 | import javax.annotation.processing.Generated; 4 | 5 | @Generated("tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor") 6 | public final class EntityWithSimpleSingularIdFields { 7 | private EntityWithSimpleSingularIdFields(){} 8 | 9 | public static final String ID = "id"; 10 | public static final String LAST_UPDATED = "lastUpdated"; 11 | public static final String PAYLOAD = "payload"; 12 | public static final String NOT_ID_OBJ = "notId"; 13 | public static final class NotId { 14 | private NotId(){} 15 | 16 | public static final String VALUE = "notId.value"; 17 | } 18 | } -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/output/KotlinDataClassFields.java: -------------------------------------------------------------------------------- 1 | package input.generated; 2 | 3 | import javax.annotation.processing.Generated; 4 | 5 | @Generated("tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor") 6 | public final class KotlinDataClassFields { 7 | private KotlinDataClassFields(){} 8 | 9 | public static final String FIELD1 = "field1"; 10 | public static final String FIELD2 = "field2"; 11 | public static final String STATUS = "status"; 12 | public static final String DATA = "data"; 13 | public static final String ID_OBJ = "id"; 14 | public static final class Id { 15 | private Id(){} 16 | 17 | public static final String NESTED_FIELD1 = "id.nestedField1"; 18 | public static final String NESTED_FIELD2 = "id.nestedField2"; 19 | } 20 | } -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/output/NoSimpleFieldsClassFields.java: -------------------------------------------------------------------------------- 1 | package some_package.generated; 2 | 3 | import javax.annotation.processing.Generated; 4 | 5 | @Generated("tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor") 6 | public final class NoSimpleFieldsClassFields { 7 | private NoSimpleFieldsClassFields(){} 8 | 9 | } -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/output/TestRecordFields.java: -------------------------------------------------------------------------------- 1 | package generated; 2 | 3 | import javax.annotation.processing.Generated; 4 | 5 | @Generated("tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor") 6 | public final class TestRecordFields { 7 | private TestRecordFields(){} 8 | 9 | public static final String FIELD_ONE = "fieldOne"; 10 | public static final String FIELD = "field"; 11 | public static final String ID = "id"; 12 | public static final String IC_OBJ = "ic"; 13 | public static final class Ic { 14 | private Ic(){} 15 | 16 | public static final String INNER_CLASS_VALUE = "ic.innerClassValue"; 17 | } 18 | } -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/output/TypicalEntityFields.java: -------------------------------------------------------------------------------- 1 | package yandex.cloud.trail.model.generated; 2 | 3 | import javax.annotation.processing.Generated; 4 | 5 | @Generated("tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor") 6 | public final class TypicalEntityFields { 7 | private TypicalEntityFields(){} 8 | 9 | public static final String LAST_UPDATED = "lastUpdated"; 10 | public static final String PAYLOAD = "payload"; 11 | public static final String METADATA = "metadata"; 12 | public static final String STATE = "state"; 13 | public static final String ID_OBJ = "id"; 14 | public static final class Id { 15 | private Id(){} 16 | 17 | public static final String TRAIL_ID = "id.trailId"; 18 | public static final String TOPIC_NAME = "id.topicName"; 19 | public static final String TOPIC_PARTITION = "id.topicPartition"; 20 | public static final String OFFSET = "id.offset"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ext-meta-generator/src/test/resources/output/UnconventionalFieldNamesFields.java: -------------------------------------------------------------------------------- 1 | package input.generated; 2 | 3 | import javax.annotation.processing.Generated; 4 | 5 | @Generated("tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor") 6 | public final class UnconventionalFieldNamesFields { 7 | private UnconventionalFieldNamesFields(){} 8 | 9 | public static final String FIELD_ONE = "fieldOne"; 10 | public static final String F_IELD_ONE = "fIeldOne"; 11 | public static final String F_IELD_TWO = "f_ieldTwo"; 12 | public static final String F__IELD_TWO = "f_IeldTwo"; 13 | public static final String _FIE_LD_WITH_UNDERSCORES = "_fie_ldWithUnderscores"; 14 | public static final String FIELD_IN__UPPER_CAMEL_CASE_OBJ = "FieldIn_UpperCamelCase"; 15 | public static final class FieldIn_UpperCamelCase { 16 | private FieldIn_UpperCamelCase(){} 17 | 18 | public static final String NESTED_FIELD_IN_UPPER_CAMEL_CASE = "FieldIn_UpperCamelCase.NestedFieldInUpperCamelCase"; 19 | } 20 | public static final String __FIELD_STARTED_WITH_UNDER_SCORE_OBJ = "_FieldStartedWithUnderScore"; 21 | public static final class _FieldStartedWithUnderScore { 22 | private _FieldStartedWithUnderScore(){} 23 | 24 | public static final String NESTED_FIELD_IN_UPPER_CAMEL_CASE = "_FieldStartedWithUnderScore.NestedFieldInUpperCamelCase"; 25 | } 26 | } -------------------------------------------------------------------------------- /json-jackson-v2/BUILD: -------------------------------------------------------------------------------- 1 | load("@contrib_rules_jvm//java:defs.bzl", "java_library", "java_test_suite") 2 | 3 | java_library( 4 | name = "json-jackson-v2", 5 | srcs = glob(["src/main/**/*.java"]), 6 | visibility = ["//visibility:public"], 7 | deps = [ 8 | "//bom:lombok", 9 | "//repository", 10 | "@java_contribs_stable//:com_fasterxml_jackson_core_jackson_annotations", 11 | "@java_contribs_stable//:com_fasterxml_jackson_core_jackson_databind", 12 | "@java_contribs_stable//:com_fasterxml_jackson_datatype_jackson_datatype_jdk8", 13 | "@java_contribs_stable//:com_fasterxml_jackson_datatype_jackson_datatype_jsr310", 14 | "@java_contribs_stable//:com_google_code_findbugs_jsr305", 15 | "@java_contribs_stable//:com_google_guava_guava", 16 | "@java_contribs_stable//:javax_annotation_javax_annotation_api", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | config.stopBubbling = true 2 | lombok.cleanup.flagUsage = error 3 | lombok.synchronized.flagUsage = error 4 | lombok.log.apacheCommons.flagUsage = error 5 | lombok.log.flogger.flagUsage = error 6 | lombok.log.javaUtilLogging.flagUsage = error 7 | lombok.log.jbosslog.flagUsage = error 8 | lombok.log.log4j.flagUsage = error 9 | lombok.log.log4j2.flagUsage = error 10 | lombok.log.xslf4j.flagUsage = error 11 | lombok.utilityClass.flagUsage = error 12 | lombok.addJavaxGeneratedAnnotation=true 13 | lombok.addLombokGeneratedAnnotation = true 14 | lombok.anyConstructor.addConstructorProperties=true 15 | lombok.equalsAndHashCode.callSuper = skip 16 | -------------------------------------------------------------------------------- /prepare-next-snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set pipefail 5 | 6 | die() { 7 | echo "$@" >&2 8 | exit 1 9 | } 10 | 11 | [ -z "$YOJ_VERSION" ] && die "Please set YOJ_VERSION before calling prepare-next-snapshot.sh!" 12 | ! (echo "$YOJ_VERSION" | grep -- '-SNAPSHOT' >/dev/null) && die "Please set YOJ_VERSION to a snapshot version (x.y.z-SNAPSHOT)!" 13 | [ -z "$MVN" ] && MVN='mvn' 14 | 15 | echo "[**] Updating YOJ artifact version to ${YOJ_VERSION}..." 16 | "$MVN" versions:set -DnewVersion="${YOJ_VERSION}" -DprocessAllModules -DgenerateBackupPoms=false 17 | 18 | echo 19 | echo "[**] Checking that YOJ ${YOJ_VERSION} builds successfully:" 20 | "$MVN" clean install 21 | 22 | echo 23 | echo "[**] Committing changes to VCS:" 24 | git add -A 25 | git commit -m "Prepare for development of YOJ ${YOJ_VERSION}" 26 | 27 | echo 28 | echo "[**] Pushing changes:" 29 | git push origin 30 | 31 | echo 32 | echo "[**] Done!" 33 | -------------------------------------------------------------------------------- /prepare-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set pipefail 5 | 6 | die() { 7 | echo "$@" >&2 8 | exit 1 9 | } 10 | 11 | [ -z "$YOJ_VERSION" ] && die "Please set YOJ_VERSION before calling prepare-release.sh!" 12 | (echo "$YOJ_VERSION" | grep -- '-SNAPSHOT' >/dev/null) && die "Please set YOJ_VERSION to a release version (x.y.z)!" 13 | [ -z "$MVN" ] && MVN='mvn' 14 | 15 | echo "[**] Updating YOJ artifact version to ${YOJ_VERSION}..." 16 | "$MVN" versions:set -DnewVersion="${YOJ_VERSION}" -DprocessAllModules -DgenerateBackupPoms=false 17 | 18 | echo 19 | echo "[**] Checking that YOJ ${YOJ_VERSION} builds successfully:" 20 | "$MVN" clean verify 21 | 22 | echo 23 | echo "[**] Updating README.md:" 24 | sed "/yoj-bom<\/artifactId>\$/ { 25 | N 26 | s|.*|${YOJ_VERSION}| 27 | }" README.md > README.md.new 28 | mv -- README.md.new README.md 29 | 30 | echo 31 | echo "[**] Committing changes to VCS:" 32 | git add -A 33 | git commit -m "Release YOJ ${YOJ_VERSION}" 34 | git tag "v${YOJ_VERSION}" 35 | 36 | echo 37 | echo "[**] Pushing changes:" 38 | git push origin && git push origin --tags 39 | 40 | echo 41 | echo "[**] Done!" 42 | echo " See https://github.com/ydb-platform/yoj-project/actions" 43 | -------------------------------------------------------------------------------- /repository-inmemory/BUILD: -------------------------------------------------------------------------------- 1 | load("@contrib_rules_jvm//java:defs.bzl", "java_library", "java_test_suite") 2 | 3 | java_library( 4 | name = "repository-inmemory", 5 | srcs = glob(["src/main/**/*.java"]), 6 | visibility = ["//visibility:public"], 7 | deps = [ 8 | "//bom:lombok", 9 | "//databind", 10 | "//repository", 11 | "//util", 12 | "@java_contribs_stable//:com_google_code_findbugs_jsr305", 13 | "@java_contribs_stable//:com_google_guava_guava", 14 | "@java_contribs_stable//:javax_annotation_javax_annotation_api", 15 | "@java_contribs_stable//:org_eclipse_collections_eclipse_collections", 16 | "@java_contribs_stable//:org_eclipse_collections_eclipse_collections_api", 17 | ], 18 | ) 19 | 20 | java_test_suite( 21 | name = "repository-inmemory-tests", 22 | srcs = glob(["src/test/java/**/*.java"]), 23 | package_prefixes = [".tech"], 24 | resources = glob(["src/test/resources/**"]), 25 | runner = "junit4", 26 | deps = [ 27 | "repository-inmemory", 28 | "//bom:lombok", 29 | "//json-jackson-v2", 30 | "//repository", 31 | "//repository-test", 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /repository-inmemory/src/main/java/tech/ydb/yoj/repository/test/inmemory/InMemoryRepositoryException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.inmemory; 2 | 3 | import tech.ydb.yoj.repository.db.exception.RepositoryException; 4 | 5 | class InMemoryRepositoryException extends RepositoryException { 6 | InMemoryRepositoryException(String message) { 7 | super(message); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /repository-inmemory/src/main/java/tech/ydb/yoj/repository/test/inmemory/ReadOnlyTxDataShard.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.inmemory; 2 | 3 | import tech.ydb.yoj.repository.db.Entity; 4 | import tech.ydb.yoj.repository.db.Table; 5 | 6 | import javax.annotation.Nullable; 7 | import java.util.List; 8 | 9 | /*package*/ interface ReadOnlyTxDataShard> { 10 | @Nullable 11 | T find(Entity.Id id); 12 | 13 | @Nullable 14 | V find(Entity.Id id, Class viewType); 15 | 16 | List findAll(); 17 | } 18 | -------------------------------------------------------------------------------- /repository-inmemory/src/main/java/tech/ydb/yoj/repository/test/inmemory/TxDataShardImpl.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.inmemory; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | import tech.ydb.yoj.repository.db.Table; 6 | 7 | import javax.annotation.Nullable; 8 | import java.util.List; 9 | 10 | @RequiredArgsConstructor 11 | /*package*/ final class TxDataShardImpl> implements ReadOnlyTxDataShard, WriteTxDataShard { 12 | private final InMemoryDataShard shard; 13 | private final long txId; 14 | private final long version; 15 | private final InMemoryTxLockWatcher watcher; 16 | 17 | @Nullable 18 | @Override 19 | public T find(Entity.Id id) { 20 | return shard.find(txId, version, id, watcher); 21 | } 22 | 23 | @Nullable 24 | @Override 25 | public V find(Entity.Id id, Class viewType) { 26 | return shard.find(txId, version, id, viewType, watcher); 27 | } 28 | 29 | @Override 30 | public List findAll() { 31 | return shard.findAll(txId, version, watcher); 32 | } 33 | 34 | @Override 35 | public void insert(T entity) { 36 | shard.insert(txId, version, entity); 37 | } 38 | 39 | @Override 40 | public void save(T entity) { 41 | shard.save(txId, version, entity); 42 | } 43 | 44 | @Override 45 | public void delete(Entity.Id id) { 46 | shard.delete(txId, id); 47 | } 48 | 49 | @Override 50 | public void deleteAll() { 51 | shard.deleteAll(txId); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /repository-inmemory/src/main/java/tech/ydb/yoj/repository/test/inmemory/WriteTxDataShard.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.inmemory; 2 | 3 | import tech.ydb.yoj.repository.db.Entity; 4 | 5 | /*package*/ interface WriteTxDataShard> { 6 | void insert(T entity); 7 | 8 | void save(T entity); 9 | 10 | void delete(Entity.Id id); 11 | 12 | void deleteAll(); 13 | } 14 | -------------------------------------------------------------------------------- /repository-inmemory/src/test/java/tech/ydb/yoj/repository/test/inmemory/InMemoryListingTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.inmemory; 2 | 3 | import tech.ydb.yoj.repository.db.Repository; 4 | import tech.ydb.yoj.repository.test.ListingTest; 5 | 6 | public class InMemoryListingTest extends ListingTest { 7 | @Override 8 | protected Repository createTestRepository() { 9 | return new TestInMemoryRepository(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /repository-inmemory/src/test/java/tech/ydb/yoj/repository/test/inmemory/InMemoryRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.inmemory; 2 | 3 | import tech.ydb.yoj.repository.db.Repository; 4 | import tech.ydb.yoj.repository.test.RepositoryTest; 5 | 6 | public class InMemoryRepositoryTest extends RepositoryTest { 7 | @Override 8 | protected Repository createTestRepository() { 9 | return new TestInMemoryRepository(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /repository-inmemory/src/test/java/tech/ydb/yoj/repository/test/inmemory/InMemoryTableQueryBuilderTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.inmemory; 2 | 3 | import tech.ydb.yoj.repository.db.Repository; 4 | import tech.ydb.yoj.repository.test.TableQueryBuilderTest; 5 | 6 | public class InMemoryTableQueryBuilderTest extends TableQueryBuilderTest { 7 | @Override 8 | protected Repository createTestRepository() { 9 | return new TestInMemoryRepository(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /repository-inmemory/src/test/resources/log4j2.yaml: -------------------------------------------------------------------------------- 1 | Configuration: 2 | appenders: 3 | Console: 4 | name: stdout 5 | PatternLayout: 6 | Pattern: "%d %-5level %-6X{tx} [%t] %c{1.}: %msg%n%throwable" 7 | 8 | Loggers: 9 | Root: 10 | level: info 11 | AppenderRef: 12 | ref: stdout 13 | Logger: 14 | - name: tech.ydb.yoj.repository.db 15 | level: debug 16 | -------------------------------------------------------------------------------- /repository-test/BUILD: -------------------------------------------------------------------------------- 1 | load("@contrib_rules_jvm//java:defs.bzl", "java_library", "java_test_suite") 2 | 3 | java_library( 4 | name = "repository-test", 5 | srcs = glob(["src/main/**/*.java"]), 6 | visibility = ["//visibility:public"], 7 | deps = [ 8 | "//bom:lombok", 9 | "//databind", 10 | "//repository", 11 | "//util", 12 | "@java_contribs_stable//:com_fasterxml_jackson_core_jackson_annotations", 13 | "@java_contribs_stable//:com_fasterxml_jackson_core_jackson_core", 14 | "@java_contribs_stable//:com_fasterxml_jackson_core_jackson_databind", 15 | "@java_contribs_stable//:com_fasterxml_jackson_datatype_jackson_datatype_jdk8", 16 | "@java_contribs_stable//:com_fasterxml_jackson_datatype_jackson_datatype_jsr310", 17 | "@java_contribs_stable//:com_google_code_findbugs_jsr305", 18 | "@java_contribs_stable//:com_google_guava_guava", 19 | "@java_contribs_stable//:javax_annotation_javax_annotation_api", 20 | "@java_contribs_stable//:junit_junit", 21 | "@java_contribs_stable//:org_assertj_assertj_core", 22 | ], 23 | ) 24 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/RepositoryTestSupport.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import tech.ydb.yoj.repository.db.Repository; 6 | import tech.ydb.yoj.repository.db.StdTxManager; 7 | import tech.ydb.yoj.repository.db.TableDescriptor; 8 | import tech.ydb.yoj.repository.db.Tx; 9 | 10 | import java.util.IdentityHashMap; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | public abstract class RepositoryTestSupport { 15 | private static final Map, Repository> repositoryMap = new IdentityHashMap<>(); 16 | 17 | protected Repository repository; 18 | 19 | protected abstract Repository createRepository(); 20 | 21 | @Before 22 | public void setUp() { 23 | this.repository = repositoryMap.computeIfAbsent(this.getClass(), aClass -> createRepository()); 24 | } 25 | 26 | @After 27 | public void tearDown() { 28 | clearDb(this.repository); 29 | } 30 | 31 | public static void clearDb(Repository repo) { 32 | Set> tableDescriptors = repo.tables(); 33 | new StdTxManager(repo).tx(() -> { 34 | for (TableDescriptor tableDescriptor : tableDescriptors) { 35 | Tx.Current.get().getRepositoryTransaction().table(tableDescriptor).deleteAll(); 36 | } 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/TeamView.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.repository.db.Table; 5 | import tech.ydb.yoj.repository.test.sample.model.Team; 6 | 7 | @Value 8 | public final class TeamView implements Table.View { 9 | Team.Id id; 10 | Team.Id parentId; 11 | } 12 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/TestDb.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample; 2 | 3 | import tech.ydb.yoj.repository.db.TxManager; 4 | 5 | public interface TestDb extends TxManager, TestEntityOperations { 6 | } 7 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/TestDbImpl.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample; 2 | 3 | import lombok.experimental.Delegate; 4 | import tech.ydb.yoj.repository.db.Repository; 5 | import tech.ydb.yoj.repository.db.StdTxManager; 6 | import tech.ydb.yoj.repository.db.Tx; 7 | import tech.ydb.yoj.repository.db.TxManager; 8 | 9 | public class TestDbImpl implements TestDb { 10 | @Delegate 11 | private final TxManager txManagerImpl; 12 | 13 | public TestDbImpl(R repository) { 14 | txManagerImpl = new StdTxManager(repository) 15 | .withLogStatementOnSuccess(true); 16 | } 17 | 18 | @Delegate 19 | protected TestEntityOperations db() { 20 | return (TestEntityOperations) Tx.Current.get().getRepositoryTransaction(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/Bubble.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | 6 | @Value 7 | public class Bubble implements Entity { 8 | Id id; 9 | 10 | String fieldA; 11 | String fieldB; 12 | String fieldC; 13 | 14 | @Value 15 | public static class Id implements Entity.Id { 16 | String a; 17 | String b; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/BytePkEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import tech.ydb.yoj.databind.ByteArray; 4 | import tech.ydb.yoj.repository.db.RecordEntity; 5 | 6 | public record BytePkEntity( 7 | Id id 8 | ) implements RecordEntity { 9 | public record Id( 10 | ByteArray array 11 | ) implements RecordEntity.Id { 12 | } 13 | 14 | public static BytePkEntity valueOf(int... array) { 15 | byte[] result = new byte[array.length]; 16 | for (int i = 0; i < array.length; i++) { 17 | result[i] = (byte) array[i]; 18 | } 19 | return new BytePkEntity(new Id(ByteArray.wrap(result))); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/ChangefeedEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.DbType; 5 | import tech.ydb.yoj.databind.schema.Changefeed; 6 | import tech.ydb.yoj.databind.schema.Column; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | 9 | import static tech.ydb.yoj.databind.schema.Changefeed.Format.JSON; 10 | import static tech.ydb.yoj.databind.schema.Changefeed.Mode.KEYS_ONLY; 11 | 12 | @Value 13 | @Changefeed( 14 | name = "test-changefeed1", 15 | mode = KEYS_ONLY, 16 | format = JSON, 17 | virtualTimestamps = true, 18 | retentionPeriod = "PT1H", 19 | initialScan = false, /* otherwise YDB is "overloaded" during YdbRepositoryIntegrationTest */ 20 | consumers = { 21 | @Changefeed.Consumer(name = "test-consumer1"), 22 | @Changefeed.Consumer( 23 | name = "test-consumer2", 24 | readFrom = "2025-01-21T08:01:25+00:00", 25 | codecs = {Changefeed.Consumer.Codec.RAW}, 26 | important = true 27 | ) 28 | } 29 | ) 30 | @Changefeed(name = "test-changefeed2") 31 | public class ChangefeedEntity implements Entity { 32 | Id id; 33 | 34 | @Column(dbType = DbType.UTF8) 35 | String stringField; 36 | 37 | @Value 38 | public static class Id implements Entity.Id { 39 | String value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/Complex.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Value; 5 | import lombok.With; 6 | import tech.ydb.yoj.repository.db.Entity; 7 | import tech.ydb.yoj.repository.db.Table; 8 | 9 | @Value 10 | @AllArgsConstructor 11 | public class Complex implements Entity { 12 | Id id; 13 | String value; 14 | 15 | @Value 16 | @With 17 | public static class Id implements Entity.Id { 18 | Integer a; 19 | Long b; 20 | String c; 21 | Status d; 22 | } 23 | 24 | public enum Status { 25 | OK, FAIL 26 | } 27 | 28 | public Complex(Id id) { 29 | this(id, ""); 30 | } 31 | 32 | @Value 33 | public static class View implements Table.View { 34 | String value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/DetachedEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import tech.ydb.yoj.repository.db.RecordEntity; 4 | 5 | public record DetachedEntity(DetachedEntityId id) implements RecordEntity { 6 | } 7 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/DetachedEntityId.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import tech.ydb.yoj.repository.db.Entity; 4 | 5 | public record DetachedEntityId(String value) implements Entity.Id { 6 | } 7 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/EntityWithTopLevelId.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | 6 | @Value 7 | public class EntityWithTopLevelId implements Entity { 8 | TopLevelId id; 9 | } 10 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/EntityWithValidation.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import com.google.common.base.Preconditions; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.Value; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | import tech.ydb.yoj.repository.db.Table; 9 | 10 | import java.beans.ConstructorProperties; 11 | 12 | @Getter 13 | @EqualsAndHashCode 14 | public class EntityWithValidation implements Entity { 15 | public static EntityWithValidation BAD_VALUE = new EntityWithValidation(); 16 | public static EntityWithValidation BAD_VALUE_IN_VIEW = new EntityWithValidation(new Id("bad-value-view"), 45L); 17 | 18 | private final Id id; 19 | private final long value; 20 | 21 | @ConstructorProperties({"id", "value"}) 22 | public EntityWithValidation(Id id, long value) { 23 | this.id = id; 24 | 25 | Preconditions.checkArgument(value != 42L, "Bad value: %s", value); 26 | this.value = value; 27 | } 28 | 29 | private EntityWithValidation() { 30 | this.id = new Id("bad-entity"); 31 | this.value = 42L; 32 | } 33 | 34 | @Value 35 | public static class Id implements Entity.Id { 36 | String value; 37 | } 38 | 39 | @Getter 40 | @EqualsAndHashCode 41 | public static class OnlyVal implements Table.View { 42 | private final long value; 43 | 44 | @ConstructorProperties({"value"}) 45 | public OnlyVal(long value) { 46 | Preconditions.checkArgument(value != 45L, "Bad value in view: %s", value); 47 | this.value = value; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/IndexedEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | import tech.ydb.yoj.databind.schema.GlobalIndex; 6 | import tech.ydb.yoj.databind.schema.Table; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | 9 | @Value 10 | @GlobalIndex(name = IndexedEntity.KEY_INDEX, fields = {"keyId"}) 11 | @GlobalIndex(name = IndexedEntity.VALUE_INDEX, fields = {"valueId", "valueId2"}) 12 | @Table(name = "table_with_indexes") 13 | public class IndexedEntity implements Entity { 14 | public static final String KEY_INDEX = "key_index"; 15 | public static final String VALUE_INDEX = "value_index"; 16 | 17 | @Column(name = "version_id") 18 | Id id; 19 | @Column(name = "key_id") 20 | String keyId; 21 | @Column(name = "value_id") 22 | String valueId; 23 | @Column 24 | String valueId2; 25 | 26 | @Value 27 | public static class Id implements Entity.Id { 28 | @Column(name = "version_id") 29 | String versionId; 30 | } 31 | 32 | @Value 33 | public static class Key { 34 | @Column(name = "value_id") 35 | String valueId; 36 | @Column 37 | String valueId2; 38 | } 39 | 40 | @Value 41 | public static class View implements tech.ydb.yoj.repository.db.Table.View { 42 | @Column(name = "version_id") 43 | String version; 44 | } 45 | 46 | @Value 47 | public static class ValueIdView implements tech.ydb.yoj.repository.db.Table.View { 48 | @Column(name = "value_id") 49 | String valueId; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/LogEntry.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.NonNull; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.Value; 6 | import tech.ydb.yoj.repository.db.Entity; 7 | import tech.ydb.yoj.repository.db.Table; 8 | 9 | @Value 10 | public class LogEntry implements Entity { 11 | // @NonNull - no annotation for testing 12 | Id id; 13 | 14 | @NonNull 15 | Level level; 16 | 17 | @NonNull 18 | String message; 19 | 20 | public enum Level { 21 | DEBUG, 22 | INFO, 23 | WARN, 24 | ERROR 25 | } 26 | 27 | @Value 28 | public static class Id implements Entity.Id { 29 | @NonNull 30 | String logId; 31 | 32 | long timestamp; 33 | } 34 | 35 | @Value 36 | @RequiredArgsConstructor 37 | public static class Message implements Table.View { 38 | @NonNull 39 | LogEntry.Id id; 40 | 41 | @NonNull 42 | String message; 43 | 44 | public Message(@NonNull LogEntry entry) { 45 | this(entry.getId(), entry.getMessage()); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/MultiLevelDirectory.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Table; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | 7 | @Value 8 | @Table(name = "multi/level/directory/for/simple_entity") 9 | public class MultiLevelDirectory implements Entity { 10 | Id id; 11 | 12 | @Value 13 | public static class Id implements Entity.Id { 14 | String value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/MultiWrappedEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.databind.converter.StringColumn; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | import tech.ydb.yoj.repository.db.RecordEntity; 7 | 8 | import javax.annotation.Nullable; 9 | 10 | public record MultiWrappedEntity( 11 | @NonNull Id id, 12 | @NonNull String payload, 13 | @Nullable OptionalPayload optionalPayload 14 | ) implements RecordEntity { 15 | public record Id( 16 | @StringColumn StringWrapper itIsReallyString 17 | ) implements Entity.Id { 18 | } 19 | 20 | public record StringWrapper(@NonNull String value) { 21 | @Override 22 | public String toString() { 23 | return value; 24 | } 25 | } 26 | 27 | public record OptionalPayload( 28 | @StringColumn @NonNull StringWrapper wrapper 29 | ) { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/MultiWrappedEntity2.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import com.google.common.base.Preconditions; 4 | import lombok.NonNull; 5 | import lombok.Value; 6 | import tech.ydb.yoj.databind.converter.StringValueType; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | import tech.ydb.yoj.repository.db.RecordEntity; 9 | 10 | public record MultiWrappedEntity2( 11 | @NonNull Id id 12 | ) implements RecordEntity { 13 | public record Id(@NonNull MultiWrappedEntity2.WrapperOfIdStringValue value) implements Entity.Id { 14 | } 15 | 16 | public record WrapperOfIdStringValue(@NonNull MultiWrappedEntity2.IdStringValue idWrapperValue) { 17 | } 18 | 19 | @Value 20 | @StringValueType 21 | public static class IdStringValue { 22 | @NonNull 23 | String regionCode; 24 | 25 | @NonNull 26 | String path; 27 | 28 | @NonNull 29 | public static MultiWrappedEntity2.IdStringValue fromString(@NonNull String serialized) { 30 | String[] parts = serialized.split(":"); 31 | Preconditions.checkArgument(parts.length == 2, "Invalid ID: %s", serialized); 32 | return new IdStringValue(parts[0], parts[1]); 33 | } 34 | 35 | @NonNull 36 | @Override 37 | public String toString() { 38 | return regionCode + ":" + path; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/NonDeserializableEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.NonNull; 4 | import lombok.Value; 5 | import tech.ydb.yoj.databind.schema.Column; 6 | import tech.ydb.yoj.repository.db.Entity; 7 | 8 | @Value 9 | public class NonDeserializableEntity implements Entity { 10 | @NonNull 11 | Id id; 12 | 13 | @NonNull 14 | @Column(flatten = false) 15 | NonDeserializableObject badObject; 16 | 17 | @Value 18 | public static class Id implements Entity.Id { 19 | @NonNull 20 | String value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/NonDeserializableObject.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.databind.DeserializationContext; 5 | import com.fasterxml.jackson.databind.JsonDeserializer; 6 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 7 | 8 | import java.io.IOException; 9 | 10 | @JsonDeserialize(using = NonDeserializableObject.Deserializer.class) 11 | public final class NonDeserializableObject { 12 | static final class Deserializer extends JsonDeserializer { 13 | @Override 14 | public NonDeserializableObject deserialize(JsonParser p, 15 | DeserializationContext ctxt) throws IOException { 16 | throw new IOException("shit happened"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/Primitive.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.NonNull; 4 | import lombok.Value; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | 7 | @Value 8 | public class Primitive implements Entity { 9 | @NonNull 10 | Id id; 11 | 12 | int value; 13 | 14 | @Value 15 | public static class Id implements Entity.Id { 16 | long id; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/Project.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import lombok.With; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | 7 | @Value 8 | public class Project implements Entity { 9 | Id id; 10 | @With 11 | String name; 12 | 13 | @Value 14 | public static class Id implements Entity.Id { 15 | String value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/Referring.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | 6 | import java.util.List; 7 | 8 | @Value 9 | public class Referring implements Entity { 10 | Id id; 11 | Project.Id project; 12 | Complex.Id complex; 13 | List projects; 14 | List complexes; 15 | 16 | @Value 17 | public static class Id implements Entity.Id { 18 | String value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/Simple.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | 6 | @Value 7 | public class Simple implements Entity { 8 | Id id; 9 | 10 | @Value 11 | public static class Id implements Entity.Id { 12 | String value; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/Supabubble.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | 6 | @Value 7 | public class Supabubble implements Entity { 8 | Id id; 9 | 10 | @Value 11 | public static class Id implements Entity.Id { 12 | Project.Id parentId; 13 | String bubbleName; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/Supabubble2.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | 6 | @Value 7 | public class Supabubble2 implements Entity { 8 | Id id; 9 | 10 | @Value 11 | public static class Id implements Entity.Id { 12 | Project.Id parentId; 13 | String bubbleName; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/Team.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | 6 | import java.util.Set; 7 | 8 | @Value 9 | public final class Team implements Entity { 10 | Id id; 11 | Id parentId; 12 | Set members; 13 | 14 | @Value 15 | public static class Id implements Entity.Id { 16 | String value; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/TopLevelId.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | 6 | @Value 7 | public class TopLevelId implements Entity.Id { 8 | String value; 9 | } 10 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/TtlEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.DbType; 5 | import tech.ydb.yoj.databind.schema.Column; 6 | import tech.ydb.yoj.databind.schema.TTL; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | 9 | import java.time.Instant; 10 | 11 | @Value 12 | @TTL(field = "createdAt", interval = "PT1H") 13 | public class TtlEntity implements Entity { 14 | Id id; 15 | 16 | @Column(dbType = DbType.TIMESTAMP) 17 | Instant createdAt; 18 | 19 | @Value 20 | public static class Id implements Entity.Id { 21 | String value; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/UniqueProject.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import lombok.With; 5 | import tech.ydb.yoj.databind.schema.GlobalIndex; 6 | import tech.ydb.yoj.repository.db.Entity; 7 | import tech.ydb.yoj.repository.db.Table; 8 | 9 | @Value 10 | @GlobalIndex(name = "unique_name", fields = {"name"}, type = GlobalIndex.Type.UNIQUE) 11 | public class UniqueProject implements Entity { 12 | Id id; 13 | @With 14 | String name; 15 | @With 16 | int version; 17 | 18 | @Value 19 | public static class Id implements Entity.Id { 20 | String value; 21 | } 22 | 23 | @Value 24 | public static class NameView implements Table.View { 25 | String name; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/Version.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.databind.converter.ValueConverter; 5 | import tech.ydb.yoj.databind.schema.Schema.JavaField; 6 | 7 | public record Version(long value) { 8 | public static final class Converter implements ValueConverter { 9 | @Override 10 | public @NonNull Long toColumn(@NonNull JavaField field, @NonNull Version v) { 11 | return v.value(); 12 | } 13 | 14 | @Override 15 | public @NonNull Version toJava(@NonNull JavaField field, @NonNull Long value) { 16 | return new Version(value); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/VersionColumn.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import tech.ydb.yoj.databind.CustomValueType; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | 6 | import java.lang.annotation.Inherited; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 11 | import static java.lang.annotation.ElementType.FIELD; 12 | import static java.lang.annotation.ElementType.RECORD_COMPONENT; 13 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 14 | 15 | @Inherited 16 | @Retention(RUNTIME) 17 | @Target({FIELD, RECORD_COMPONENT, ANNOTATION_TYPE}) 18 | @Column(customValueType = @CustomValueType(columnClass = Long.class, converter = Version.Converter.class)) 19 | public @interface VersionColumn {} 20 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/VersionedAliasedEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import tech.ydb.yoj.databind.converter.StringColumn; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | import tech.ydb.yoj.repository.db.RecordEntity; 6 | import tech.ydb.yoj.repository.test.sample.model.annotations.Sha256; 7 | import tech.ydb.yoj.repository.test.sample.model.annotations.UniqueEntity; 8 | 9 | import java.util.UUID; 10 | 11 | public record VersionedAliasedEntity( 12 | Id id, 13 | @VersionColumn 14 | Version version2, 15 | @StringColumn 16 | UUID uuid, 17 | UniqueEntity.Id uniqueId 18 | ) implements RecordEntity { 19 | public record Id( 20 | String value, 21 | @VersionColumn 22 | Version version, 23 | @StringColumn 24 | UUID uuidId, 25 | Sha256 hash 26 | ) implements Entity.Id { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/VersionedEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import tech.ydb.yoj.databind.CustomValueType; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | import tech.ydb.yoj.repository.db.RecordEntity; 7 | 8 | public record VersionedEntity( 9 | Id id, 10 | @Column( 11 | customValueType = @CustomValueType( 12 | columnClass = Long.class, 13 | converter = Version.Converter.class 14 | ) 15 | ) 16 | Version version2 17 | ) implements RecordEntity { 18 | public record Id( 19 | String value, 20 | @Column( 21 | customValueType = @CustomValueType( 22 | columnClass = Long.class, 23 | converter = Version.Converter.class 24 | ) 25 | ) 26 | Version version 27 | ) implements Entity.Id { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/WithUnflattenableField.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | 7 | @Value 8 | public class WithUnflattenableField implements Entity { 9 | Id id; 10 | 11 | @Column(flatten = false) 12 | Unflattenable unflattenable; 13 | 14 | @Value 15 | public static final class Unflattenable { 16 | String str; 17 | int integer; 18 | } 19 | 20 | @Value 21 | public static final class Id implements Entity.Id { 22 | String value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/annotations/Digest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model.annotations; 2 | 3 | import java.util.Objects; 4 | 5 | 6 | public class Digest implements YojString { 7 | private final String algorithm; 8 | private final String digest; 9 | 10 | protected Digest(String algorithm, String digest) { 11 | this.algorithm = algorithm; 12 | this.digest = digest; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return algorithm + ":" + digest; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object object) { 22 | if (this == object) { 23 | return true; 24 | } 25 | if (object == null || getClass() != object.getClass()) { 26 | return false; 27 | } 28 | Digest digest1 = (Digest) object; 29 | return Objects.equals(algorithm, digest1.algorithm) && Objects.equals(digest, digest1.digest); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(algorithm, digest); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/annotations/Sha256.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model.annotations; 2 | 3 | public class Sha256 extends Digest { 4 | private static final String SHA_256 = "SHA256"; 5 | 6 | public Sha256(String digest) { 7 | super(SHA_256, digest); 8 | } 9 | 10 | public static Sha256 valueOf(String value) { 11 | String[] parsed = value.split(":"); 12 | if (parsed.length != 2 || !SHA_256.equals(parsed[0])) { 13 | throw new IllegalArgumentException(); 14 | } 15 | return new Sha256(parsed[1]); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/annotations/UniqueEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model.annotations; 2 | 3 | import tech.ydb.yoj.repository.db.Entity; 4 | import tech.ydb.yoj.repository.db.RecordEntity; 5 | 6 | import java.util.UUID; 7 | 8 | public record UniqueEntity(Id id, String value) implements RecordEntity { 9 | public record Id(UUID id) implements Entity.Id { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/annotations/UniqueEntityNative.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model.annotations; 2 | 3 | import tech.ydb.yoj.databind.DbType; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | import tech.ydb.yoj.repository.db.RecordEntity; 7 | 8 | import java.util.UUID; 9 | 10 | public record UniqueEntityNative(Id id, String value) implements RecordEntity { 11 | public record Id( 12 | @Column(dbType = DbType.UUID) 13 | UUID id 14 | ) implements Entity.Id { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /repository-test/src/main/java/tech/ydb/yoj/repository/test/sample/model/annotations/YojString.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.test.sample.model.annotations; 2 | 3 | import tech.ydb.yoj.databind.converter.StringValueType; 4 | 5 | @StringValueType 6 | public interface YojString { 7 | } 8 | -------------------------------------------------------------------------------- /repository-ydb-common/BUILD: -------------------------------------------------------------------------------- 1 | load("@contrib_rules_jvm//java:defs.bzl", "java_library", "java_test_suite") 2 | 3 | java_library( 4 | name = "repository-ydb-common", 5 | srcs = glob(["src/main/**/*.java"]), 6 | visibility = ["//visibility:public"], 7 | deps = [ 8 | "//bom:lombok", 9 | "//databind", 10 | "//repository", 11 | "//util", 12 | "@java_contribs_stable//:io_prometheus_simpleclient", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /repository-ydb-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | yoj-repository-ydb-common 8 | jar 9 | 10 | 11 | tech.ydb.yoj 12 | yoj-parent 13 | 2.6.19-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | YOJ - YDB Repository Common Logic 18 | 19 | Common Logic for all YDB Repository implementations, regardless of the YDB SDK major version used. 20 | 21 | 22 | 23 | 24 | tech.ydb.yoj 25 | yoj-databind 26 | 27 | 28 | tech.ydb.yoj 29 | yoj-util 30 | 31 | 32 | tech.ydb.yoj 33 | yoj-repository 34 | 35 | 36 | 37 | org.slf4j 38 | slf4j-api 39 | 40 | 41 | io.prometheus 42 | simpleclient 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/BadSessionException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | import tech.ydb.yoj.repository.db.exception.RetryableException; 4 | 5 | /** 6 | * Tried to use a no longer active or valid YDB session, e.g. on a node that is now down. 7 | */ 8 | public class BadSessionException extends RetryableException { 9 | public BadSessionException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/ResultTruncatedException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | public class ResultTruncatedException extends YdbRepositoryException { 4 | public ResultTruncatedException(String message, Object request, Object response) { 5 | super(message, request, response); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/SnapshotCreateException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | public class SnapshotCreateException extends YdbRepositoryException { 4 | public SnapshotCreateException(String s) { 5 | super(s); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/UnexpectedException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | public class UnexpectedException extends YdbRepositoryException { 4 | public UnexpectedException(String message) { 5 | super(message); 6 | } 7 | 8 | public UnexpectedException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbClientInternalException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | import tech.ydb.yoj.repository.db.exception.RepositoryException; 4 | import tech.ydb.yoj.repository.db.exception.RetryableException; 5 | import tech.ydb.yoj.repository.db.exception.UnavailableException; 6 | import tech.ydb.yoj.util.lang.Strings; 7 | import tech.ydb.yoj.util.retry.RetryPolicy; 8 | 9 | /** 10 | * Internal YDB SDK exception, caused by a transport failure, internal authorization/authentication error etc. 11 | */ 12 | public final class YdbClientInternalException extends RetryableException { 13 | private static final RetryPolicy RETRY_POLICY = RetryPolicy.fixed(100L, 0.2); 14 | 15 | public YdbClientInternalException(Object request, Object response) { 16 | super(Strings.join("\n", request, response), RETRY_POLICY); 17 | } 18 | 19 | @Override 20 | public RepositoryException rethrow() { 21 | return UnavailableException.afterRetries("YDB SDK internal exception, retries failed", this); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbComponentUnavailableException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | import tech.ydb.yoj.repository.db.exception.RepositoryException; 4 | import tech.ydb.yoj.repository.db.exception.RetryableException; 5 | import tech.ydb.yoj.repository.db.exception.UnavailableException; 6 | import tech.ydb.yoj.util.lang.Strings; 7 | import tech.ydb.yoj.util.retry.RetryPolicy; 8 | 9 | /** 10 | * One or more YDB components are not available, but the YDB API was still able to respond. 11 | */ 12 | public final class YdbComponentUnavailableException extends RetryableException { 13 | private static final RetryPolicy UNAVAILABLE_RETRY_POLICY = RetryPolicy.fixed(100L, 0.2); 14 | 15 | public YdbComponentUnavailableException(Object request, Object response) { 16 | super(Strings.join("\n", request, response), UNAVAILABLE_RETRY_POLICY); 17 | } 18 | 19 | public YdbComponentUnavailableException(String message, Throwable t) { 20 | super(message, UNAVAILABLE_RETRY_POLICY, t); 21 | } 22 | 23 | @Override 24 | public RepositoryException rethrow() { 25 | return UnavailableException.afterRetries("Database is partially unavailable, retries failed", this); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbOverloadedException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | import tech.ydb.yoj.repository.db.exception.RepositoryException; 4 | import tech.ydb.yoj.repository.db.exception.RetryableException; 5 | import tech.ydb.yoj.repository.db.exception.UnavailableException; 6 | import tech.ydb.yoj.util.lang.Strings; 7 | import tech.ydb.yoj.util.retry.RetryPolicy; 8 | 9 | /** 10 | * YDB node is overloaded, but the YDB API was still able to respond. 11 | */ 12 | public final class YdbOverloadedException extends RetryableException { 13 | private static final RetryPolicy OVERLOADED_BACKOFF = RetryPolicy.expBackoff(100L, 1_000L, 0.1, 2.0); 14 | 15 | public YdbOverloadedException(Object request, Object response) { 16 | super(Strings.join("\n", request, response), OVERLOADED_BACKOFF); 17 | } 18 | 19 | @Override 20 | public RepositoryException rethrow() { 21 | return UnavailableException.afterRetries("Database overloaded, retries failed", this); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbRepositoryException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | import tech.ydb.yoj.repository.db.exception.RepositoryException; 4 | import tech.ydb.yoj.util.lang.Strings; 5 | 6 | /** 7 | * Base class for non-retryable YDB-specific exceptions. 8 | */ 9 | public class YdbRepositoryException extends RepositoryException { 10 | public YdbRepositoryException(Object request, Object response) { 11 | this(null, request, response); 12 | } 13 | 14 | public YdbRepositoryException(String message, Object request, Object response) { 15 | this(Strings.join("\n", message, request, response)); 16 | } 17 | 18 | public YdbRepositoryException(String message) { 19 | super(message); 20 | } 21 | 22 | public YdbRepositoryException(String message, Throwable cause) { 23 | super(message, cause); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbSchemaException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | /** 4 | * Base class for database schema problems, e.g. table not found. 5 | */ 6 | public class YdbSchemaException extends YdbRepositoryException { 7 | public YdbSchemaException(String message, Object request, Object response) { 8 | super(message, request, response); 9 | } 10 | 11 | public YdbSchemaException(String message) { 12 | super(message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbSchemaPathNotFoundException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | public class YdbSchemaPathNotFoundException extends YdbSchemaException { 4 | public YdbSchemaPathNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbUnauthenticatedException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | import tech.ydb.yoj.repository.db.exception.RepositoryException; 4 | import tech.ydb.yoj.repository.db.exception.RetryableException; 5 | import tech.ydb.yoj.repository.db.exception.UnavailableException; 6 | import tech.ydb.yoj.util.lang.Strings; 7 | 8 | /** 9 | * YDB authentication failure, possibly a transient one. E.g., used a recently expired token. 10 | */ 11 | public class YdbUnauthenticatedException extends RetryableException { 12 | public YdbUnauthenticatedException(Object request, Object response) { 13 | super(Strings.join("\n", request, response)); 14 | } 15 | 16 | @Override 17 | public RepositoryException rethrow() { 18 | return UnavailableException.afterRetries("DB authentication failed after retries", this); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbUnauthorizedException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.exception; 2 | 3 | import tech.ydb.yoj.repository.db.exception.RepositoryException; 4 | import tech.ydb.yoj.repository.db.exception.RetryableException; 5 | import tech.ydb.yoj.repository.db.exception.UnavailableException; 6 | import tech.ydb.yoj.util.lang.Strings; 7 | 8 | /** 9 | * YDB authorization failure, possibly a transient one. E.g., the principal tried to write to the database but has no 10 | * write-allowing role assigned. 11 | */ 12 | public class YdbUnauthorizedException extends RetryableException { 13 | public YdbUnauthorizedException(Object request, Object response) { 14 | super(Strings.join("\n", request, response)); 15 | } 16 | 17 | @Override 18 | public RepositoryException rethrow() { 19 | return UnavailableException.afterRetries("Access to database denied, retries failed", this); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/YdbLegacySpliterator.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb; 2 | 3 | import tech.ydb.yoj.InternalApi; 4 | 5 | import java.util.Spliterator; 6 | import java.util.function.Consumer; 7 | import java.util.stream.Stream; 8 | import java.util.stream.StreamSupport; 9 | 10 | /** 11 | * @deprecated Legacy implementation of {@code Spliterator} for {@code ReadTable}. Will be eventually removed in a future YOJ version. 12 | *

To use the new {@code Spliterator} contract-conformant implementation, set {@code ReadTableParams.builder().<...>.useNewSpliterator(true)}. 13 | *

Note that using the new implementation currently has a negative performance impact, for more information refer to 14 | * GitHub Issue #42. 15 | */ 16 | @Deprecated 17 | @InternalApi 18 | public class YdbLegacySpliterator implements Spliterator { 19 | private final int flags; 20 | private final Consumer> action; 21 | 22 | public YdbLegacySpliterator(boolean isOrdered, Consumer> action) { 23 | this.action = action; 24 | flags = (isOrdered ? ORDERED : 0) | NONNULL; 25 | } 26 | 27 | public Stream makeStream() { 28 | return StreamSupport.stream(this, false); 29 | } 30 | 31 | @Override 32 | public boolean tryAdvance(Consumer action) { 33 | this.action.accept(action); 34 | return false; 35 | } 36 | 37 | @Override 38 | public Spliterator trySplit() { 39 | return null; 40 | } 41 | 42 | @Override 43 | public long estimateSize() { 44 | return Long.MAX_VALUE; 45 | } 46 | 47 | @Override 48 | public int characteristics() { 49 | return flags; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/bulk/BulkMapper.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.bulk; 2 | 3 | import tech.ydb.proto.ValueProtos; 4 | import tech.ydb.yoj.InternalApi; 5 | 6 | import java.util.Map; 7 | 8 | @InternalApi 9 | public interface BulkMapper { 10 | String getTableName(String tableSpace); 11 | 12 | Map map(E entity); 13 | } 14 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/client/QueryInterceptor.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.client; 2 | 3 | import tech.ydb.table.Session; 4 | 5 | public interface QueryInterceptor { 6 | void beforeExecute(QueryInterceptingSession.QueryType type, Session session, String query); 7 | } 8 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/client/SessionManager.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.client; 2 | 3 | import tech.ydb.table.Session; 4 | 5 | public interface SessionManager extends AutoCloseable { 6 | Session getSession(); 7 | 8 | void release(Session session); 9 | 10 | void warmup(); 11 | 12 | void invalidateAllSessions(); 13 | 14 | void shutdown(); 15 | 16 | @Override 17 | default void close() { 18 | shutdown(); 19 | } 20 | 21 | default boolean healthCheck() { 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/client/YdbIssue.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.client; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import tech.ydb.core.Issue; 5 | import tech.ydb.yoj.InternalApi; 6 | 7 | import static lombok.AccessLevel.PRIVATE; 8 | 9 | @InternalApi 10 | @RequiredArgsConstructor(access = PRIVATE) 11 | public enum YdbIssue { 12 | DEFAULT_ERROR(0), 13 | CONSTRAINT_VIOLATION(2012); 14 | 15 | private final int issueCode; 16 | 17 | public boolean isContainedIn(Issue[] messages) { 18 | for (Issue message : messages) { 19 | if (issueCode == message.getCode() || isContainedIn(message.getIssues())) { 20 | return true; 21 | } 22 | } 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/client/YdbPaths.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.client; 2 | 3 | import com.google.common.base.Preconditions; 4 | import tech.ydb.yoj.InternalApi; 5 | 6 | @InternalApi 7 | public final class YdbPaths { 8 | private YdbPaths() { 9 | } 10 | 11 | public static String canonicalTablespace(String tablespace) { 12 | Preconditions.checkArgument(tablespace.startsWith("/"), "tablespace must be an absolute path, but got: '%s'", tablespace); 13 | return tablespace.endsWith("/") ? tablespace : tablespace + "/"; 14 | } 15 | 16 | public static String canonicalRootDir(String tablespace) { 17 | Preconditions.checkArgument(tablespace.startsWith("/"), "tablespace must be an absolute path, but got: '%s'", tablespace); 18 | return tablespace.endsWith("/") ? tablespace.substring(0, tablespace.length() - 1) : tablespace; 19 | } 20 | 21 | public static String canonicalDatabase(String database) { 22 | Preconditions.checkArgument(database.startsWith("/"), "database path must be absolute, but got: '%s'", database); 23 | return database.endsWith("/") ? database.substring(0, database.length() - 1) : database; 24 | } 25 | 26 | public static String join(String parent, String child) { 27 | return parent.isEmpty() ? child : (parent.endsWith("/") ? parent : parent + "/") + child; 28 | } 29 | 30 | public static String tableDirectory(String tablePath) { 31 | if (!tablePath.contains("/")) { 32 | return null; 33 | } 34 | return tablePath.substring(0, tablePath.lastIndexOf("/")); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/client/interceptors/CommonFullScanCallback.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.client.interceptors; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.function.Consumer; 7 | 8 | public final class CommonFullScanCallback { 9 | private static final Logger log = LoggerFactory.getLogger(CommonFullScanCallback.class); 10 | 11 | public static final Consumer DEFAULT = query -> log.warn("FullScan", new IllegalArgumentException("\"" + query + "\"")); 12 | public static final Consumer FAIL = query -> { 13 | throw new UnexpectedFullScanQueryError(query); 14 | }; 15 | 16 | private CommonFullScanCallback() { 17 | } 18 | 19 | public static final class UnexpectedFullScanQueryError extends AssertionError { 20 | public UnexpectedFullScanQueryError(String query) { 21 | super("Unexpected full scan query : " + query); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/merge/YqlQueriesMerger.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.merge; 2 | 3 | import tech.ydb.yoj.InternalApi; 4 | import tech.ydb.yoj.repository.ydb.YdbRepository; 5 | 6 | import java.util.List; 7 | 8 | @InternalApi 9 | public interface YqlQueriesMerger { 10 | void onNext(YdbRepository.Query query); 11 | 12 | List> getQueries(); 13 | } 14 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/readtable/ReadTableMapper.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.readtable; 2 | 3 | import tech.ydb.proto.ValueProtos; 4 | import tech.ydb.yoj.InternalApi; 5 | 6 | import java.util.List; 7 | 8 | @InternalApi 9 | public interface ReadTableMapper { 10 | String getTableName(String tableSpace); 11 | 12 | List mapKey(ID id); 13 | 14 | List getColumns(); 15 | 16 | RESULT mapResult(List columnList, ValueProtos.Value value); 17 | } 18 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/statement/Count.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.statement; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.DbType; 5 | import tech.ydb.yoj.databind.schema.Column; 6 | 7 | @Value 8 | public class Count { 9 | @Column(dbType = DbType.UINT64) 10 | long count; 11 | } 12 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/statement/DeleteAllStatement.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.statement; 2 | 3 | import tech.ydb.yoj.repository.db.Entity; 4 | import tech.ydb.yoj.repository.db.EntitySchema; 5 | import tech.ydb.yoj.repository.db.TableDescriptor; 6 | 7 | public class DeleteAllStatement> extends YqlStatement { 8 | public DeleteAllStatement(TableDescriptor tableDescriptor, EntitySchema schema) { 9 | super(tableDescriptor, schema, schema); 10 | } 11 | 12 | @Override 13 | public String getQuery(String tablespace) { 14 | return "DELETE FROM " + table(tablespace); 15 | } 16 | 17 | @Override 18 | public QueryType getQueryType() { 19 | return QueryType.DELETE_ALL; 20 | } 21 | 22 | @Override 23 | public String toDebugString(PARAMS params) { 24 | return "deleteAll(" + tableDescriptor.toDebugString() + ")"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/statement/DeleteByIdStatement.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.statement; 2 | 3 | import tech.ydb.yoj.repository.db.Entity; 4 | import tech.ydb.yoj.repository.db.EntitySchema; 5 | import tech.ydb.yoj.repository.db.TableDescriptor; 6 | 7 | import java.util.Map; 8 | import java.util.function.Function; 9 | 10 | public class DeleteByIdStatement> extends MultipleVarsYqlStatement.Simple { 11 | public DeleteByIdStatement(TableDescriptor tableDescriptor, EntitySchema schema) { 12 | super(tableDescriptor, schema); 13 | } 14 | 15 | @Override 16 | public QueryType getQueryType() { 17 | return QueryType.DELETE; 18 | } 19 | 20 | @Override 21 | public String getQuery(String tablespace) { 22 | return declarations() + 23 | "DELETE FROM " + table(tablespace) + " ON SELECT * FROM AS_TABLE(" + listName + ")"; 24 | } 25 | 26 | @SuppressWarnings("unchecked") 27 | @Override 28 | protected Function> flattenInputVariables() { 29 | return t -> schema.flattenId((Entity.Id) t); 30 | } 31 | 32 | @Override 33 | public String toDebugString(IN in) { 34 | return "delete(" + toDebugParams(in) + ")"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/statement/FindAllYqlStatement.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.statement; 2 | 3 | import tech.ydb.yoj.databind.schema.Schema; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | import tech.ydb.yoj.repository.db.EntitySchema; 6 | import tech.ydb.yoj.repository.db.TableDescriptor; 7 | 8 | public class FindAllYqlStatement, RESULT> extends YqlStatement { 9 | 10 | public FindAllYqlStatement( 11 | TableDescriptor tableDescriptor, EntitySchema schema, Schema resultSchema 12 | ) { 13 | super(tableDescriptor, schema, resultSchema); 14 | } 15 | 16 | @Override 17 | public String getQuery(String tablespace) { 18 | return declarations() 19 | + "SELECT " + outNames() 20 | + " FROM " + table(tablespace) 21 | + " " + ORDER_BY_ID_ASCENDING.toFullYql(schema); 22 | } 23 | 24 | @Override 25 | public QueryType getQueryType() { 26 | return QueryType.SELECT; 27 | } 28 | 29 | @Override 30 | public String toDebugString(PARAMS params) { 31 | return "findAll(" + tableDescriptor.toDebugString() + ")"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/statement/InsertYqlStatement.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.statement; 2 | 3 | import tech.ydb.yoj.repository.db.Entity; 4 | import tech.ydb.yoj.repository.db.EntitySchema; 5 | import tech.ydb.yoj.repository.db.TableDescriptor; 6 | 7 | import java.util.Map; 8 | import java.util.function.Function; 9 | 10 | public class InsertYqlStatement> extends MultipleVarsYqlStatement.Simple { 11 | public InsertYqlStatement(TableDescriptor tableDescriptor, EntitySchema schema) { 12 | super(tableDescriptor, schema); 13 | } 14 | 15 | @Override 16 | public QueryType getQueryType() { 17 | return QueryType.INSERT; 18 | } 19 | 20 | @Override 21 | public String toDebugString(PARAMS params) { 22 | return "insert(" + toDebugParams(params) + ")"; 23 | } 24 | 25 | @Override 26 | public String getQuery(String tablespace) { 27 | return declarations() + 28 | "INSERT INTO " + table(tablespace) + " SELECT * FROM AS_TABLE(" + listName + ")"; 29 | } 30 | 31 | @SuppressWarnings("unchecked") 32 | @Override 33 | protected Function> flattenInputVariables() { 34 | return t -> schema.flatten((ENTITY) t); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/statement/UpsertYqlStatement.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.statement; 2 | 3 | import tech.ydb.yoj.repository.db.Entity; 4 | import tech.ydb.yoj.repository.db.EntitySchema; 5 | import tech.ydb.yoj.repository.db.TableDescriptor; 6 | 7 | import java.util.Map; 8 | import java.util.function.Function; 9 | 10 | public class UpsertYqlStatement> extends MultipleVarsYqlStatement.Simple { 11 | public UpsertYqlStatement(TableDescriptor tableDescriptor, EntitySchema schema) { 12 | super(tableDescriptor, schema); 13 | } 14 | 15 | @Override 16 | public QueryType getQueryType() { 17 | return QueryType.UPSERT; 18 | } 19 | 20 | @Override 21 | public String toDebugString(IN in) { 22 | return "upsert(" + toDebugParams(in) + ")"; 23 | } 24 | 25 | @Override 26 | public String getQuery(String tablespace) { 27 | return declarations() + 28 | "UPSERT INTO " + table(tablespace) + " SELECT * FROM AS_TABLE(" + listName + ")"; 29 | } 30 | 31 | @SuppressWarnings("unchecked") 32 | @Override 33 | protected Function> flattenInputVariables() { 34 | return t -> schema.flatten((T) t); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/statement/YqlStatementParam.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.statement; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | import tech.ydb.yoj.repository.ydb.yql.YqlType; 7 | 8 | @Getter 9 | @AllArgsConstructor 10 | @ToString 11 | public class YqlStatementParam { 12 | YqlType type; 13 | String name; 14 | 15 | boolean optional; 16 | 17 | public static YqlStatementParam optional(YqlType type, String name) { 18 | return new YqlStatementParam(type, name, true); 19 | } 20 | 21 | public static YqlStatementParam required(YqlType type, String name) { 22 | return new YqlStatementParam(type, name, false); 23 | } 24 | 25 | public String getVar() { 26 | return "$" + name; 27 | } 28 | 29 | @Override 30 | public final int hashCode() { 31 | return name.hashCode(); 32 | } 33 | 34 | @Override 35 | public final boolean equals(Object o) { 36 | return o instanceof YqlStatementParam && ((YqlStatementParam) o).name.equals(this.name); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlStatementPart.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.yql; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | import tech.ydb.yoj.repository.db.EntitySchema; 6 | 7 | import java.util.List; 8 | 9 | public interface YqlStatementPart

> { 10 | String getType(); 11 | 12 | int getPriority(); 13 | 14 | default > String toFullYql(@NonNull EntitySchema schema) { 15 | return getYqlPrefix() + toYql(schema); 16 | } 17 | 18 | String getYqlPrefix(); 19 | 20 | > String toYql(@NonNull EntitySchema schema); 21 | 22 | default List> combine(@NonNull List other) { 23 | throw new UnsupportedOperationException("Multiple " + getType() + " specifications are not supported"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlType.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.yql; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.proto.ValueProtos; 5 | import tech.ydb.yoj.databind.schema.Column; 6 | import tech.ydb.yoj.databind.schema.Schema.JavaField; 7 | 8 | import java.lang.reflect.Type; 9 | 10 | public interface YqlType { 11 | ValueProtos.Type.Builder getYqlTypeBuilder(); 12 | 13 | /** 14 | * @deprecated This method will be removed in YOJ 3.0.0. Nothing in YOJ calls {@code YqlType.of(Type)} any more. 15 | *

Please use {@link #of(JavaField) YqlType.of(JavaField)} because it correcly 16 | * respects the customizations specified in the {@link Column @Column} annotation. 17 | */ 18 | @NonNull 19 | @Deprecated(forRemoval = true) 20 | static YqlPrimitiveType of(Type javaType) { 21 | return YqlPrimitiveType.of(javaType); 22 | } 23 | 24 | /** 25 | * Returns the Yql type of the column. 26 | *

27 | * If the {@link Column} annotation is specified for the {@code column} field, 28 | * the annotation field {@code dbType} may be used to specify the column type. 29 | * 30 | * @return the Yql type of the column 31 | */ 32 | @NonNull 33 | static YqlPrimitiveType of(JavaField column) { 34 | return YqlPrimitiveType.of(column); 35 | } 36 | 37 | String getYqlTypeName(); 38 | 39 | ValueProtos.Value.Builder toYql(Object value); 40 | 41 | Object fromYql(ValueProtos.Value value); 42 | } 43 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/NonSerializableEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb; 2 | 3 | import lombok.NonNull; 4 | import lombok.Value; 5 | import tech.ydb.yoj.databind.schema.Column; 6 | import tech.ydb.yoj.repository.db.Entity; 7 | 8 | @Value 9 | public class NonSerializableEntity implements Entity { 10 | @NonNull 11 | Id id; 12 | 13 | @NonNull 14 | @Column(flatten = false) 15 | NonSerializableObject badObject; 16 | 17 | @Value 18 | public static class Id implements Entity.Id { 19 | @NonNull 20 | String value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/NonSerializableObject.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 7 | import com.fasterxml.jackson.databind.jsontype.TypeSerializer; 8 | 9 | import java.io.IOException; 10 | 11 | @JsonSerialize(using = NonSerializableObject.Serializer.class) 12 | final class NonSerializableObject { 13 | static final class Serializer extends JsonSerializer { 14 | @Override 15 | public void serializeWithType(NonSerializableObject value, 16 | JsonGenerator gen, SerializerProvider serializers, 17 | TypeSerializer typeSer) throws IOException { 18 | serialize(value, gen, serializers); 19 | } 20 | 21 | @Override 22 | public void serialize(NonSerializableObject value, 23 | JsonGenerator gen, SerializerProvider serializers) throws IOException { 24 | throw new IOException("shit happened"); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/SubdirEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Table; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | 7 | @Table(name = "subdir/SubdirEntity") 8 | @Value 9 | public class SubdirEntity implements Entity { 10 | 11 | Id id; 12 | 13 | @Value 14 | public static class Id implements Entity.Id { 15 | int id; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/merge/YqlQueryMergerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.merge; 2 | 3 | import org.junit.Assert; 4 | import org.junit.ClassRule; 5 | import org.junit.Test; 6 | import tech.ydb.yoj.repository.db.Repository; 7 | import tech.ydb.yoj.repository.test.RepositoryTestSupport; 8 | import tech.ydb.yoj.repository.test.entity.TestEntities; 9 | import tech.ydb.yoj.repository.test.sample.TestDb; 10 | import tech.ydb.yoj.repository.test.sample.TestDbImpl; 11 | import tech.ydb.yoj.repository.test.sample.model.Project; 12 | import tech.ydb.yoj.repository.ydb.TestYdbRepository; 13 | import tech.ydb.yoj.repository.ydb.YdbEnvAndTransportRule; 14 | import tech.ydb.yoj.repository.ydb.util.RandomUtils; 15 | 16 | public class YqlQueryMergerIntegrationTest extends RepositoryTestSupport { 17 | @ClassRule 18 | public static final YdbEnvAndTransportRule ydbEnvAndTransport = new YdbEnvAndTransportRule(); 19 | 20 | protected TestDb db; 21 | 22 | @Override 23 | public void setUp() { 24 | super.setUp(); 25 | this.db = new TestDbImpl<>(this.repository); 26 | } 27 | 28 | @Override 29 | public void tearDown() { 30 | this.db = null; 31 | super.tearDown(); 32 | } 33 | 34 | @Override 35 | protected final Repository createRepository() { 36 | return TestEntities.init(new TestYdbRepository(ydbEnvAndTransport.getYdbConfig(), ydbEnvAndTransport.getGrpcTransport())); 37 | } 38 | 39 | @Test 40 | public void insertAfterFindByIdWorks() { 41 | db.tx(() -> { 42 | Project.Id id = new Project.Id(RandomUtils.nextString(10)); 43 | 44 | Assert.assertNull(db.projects().find(id)); 45 | 46 | db.projects().insert(new Project(id, "project-1")); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/model/EntityChangeTtl.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.DbType; 5 | import tech.ydb.yoj.databind.schema.Column; 6 | import tech.ydb.yoj.databind.schema.TTL; 7 | import tech.ydb.yoj.databind.schema.Table; 8 | import tech.ydb.yoj.repository.db.Entity; 9 | 10 | import java.time.Instant; 11 | 12 | @Value 13 | @TTL(field = "createdAt", interval = "PT2H") 14 | @Table(name = "TtlEntity") 15 | public class EntityChangeTtl implements Entity { 16 | EntityChangeTtl.Id id; 17 | 18 | @Column(dbType = DbType.TIMESTAMP) 19 | Instant createdAt; 20 | 21 | @Value 22 | public static class Id implements Entity.Id { 23 | String value; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/model/EntityDropTtl.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.DbType; 5 | import tech.ydb.yoj.databind.schema.Column; 6 | import tech.ydb.yoj.databind.schema.Table; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | 9 | import java.time.Instant; 10 | 11 | @Value 12 | @Table(name = "TtlEntity") 13 | public class EntityDropTtl implements Entity { 14 | Id id; 15 | 16 | @Column(dbType = DbType.TIMESTAMP) 17 | Instant createdAt; 18 | 19 | @Value 20 | public static class Id implements Entity.Id { 21 | String value; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/model/IndexedEntityChangeIndex.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | import tech.ydb.yoj.databind.schema.GlobalIndex; 6 | import tech.ydb.yoj.databind.schema.Table; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | 9 | @Value 10 | @GlobalIndex(name = "key_index", fields = {"keyId"}) 11 | @GlobalIndex(name = "value_index", fields = {"valueId"}) 12 | @Table(name = "table_with_indexes") 13 | public class IndexedEntityChangeIndex implements Entity { 14 | @Column(name = "version_id") 15 | Id id; 16 | @Column(name = "key_id") 17 | String keyId; 18 | @Column(name = "value_id") 19 | String valueId; 20 | @Column 21 | String valueId2; 22 | 23 | @Value 24 | public static class Id implements Entity.Id { 25 | @Column(name = "version_id") 26 | String versionId; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/model/IndexedEntityCreateIndex.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | import tech.ydb.yoj.databind.schema.GlobalIndex; 6 | import tech.ydb.yoj.databind.schema.Table; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | 9 | @Value 10 | @GlobalIndex(name = "key_index", fields = {"keyId"}) 11 | @GlobalIndex(name = "value_index", fields = {"valueId", "valueId2"}) 12 | @GlobalIndex(name = "key2_index", fields = {"keyId", "valueId2"}) 13 | @Table(name = "table_with_indexes") 14 | public class IndexedEntityCreateIndex implements Entity { 15 | @Column(name = "version_id") 16 | Id id; 17 | @Column(name = "key_id") 18 | String keyId; 19 | @Column(name = "value_id") 20 | String valueId; 21 | @Column 22 | String valueId2; 23 | 24 | @Value 25 | public static class Id implements Entity.Id { 26 | @Column(name = "version_id") 27 | String versionId; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/model/IndexedEntityDropIndex.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | import tech.ydb.yoj.databind.schema.GlobalIndex; 6 | import tech.ydb.yoj.databind.schema.Table; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | 9 | @Value 10 | @GlobalIndex(name = "value_index", fields = {"valueId", "valueId2"}) 11 | @Table(name = "table_with_indexes") 12 | public class IndexedEntityDropIndex implements Entity { 13 | @Column(name = "version_id") 14 | Id id; 15 | @Column(name = "key_id") 16 | String keyId; 17 | @Column(name = "value_id") 18 | String valueId; 19 | @Column 20 | String valueId2; 21 | 22 | @Value 23 | public static class Id implements Entity.Id { 24 | @Column(name = "version_id") 25 | String versionId; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/model/IndexedEntityNew.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.model; 2 | 3 | import lombok.Value; 4 | import tech.ydb.yoj.databind.schema.Column; 5 | import tech.ydb.yoj.databind.schema.GlobalIndex; 6 | import tech.ydb.yoj.databind.schema.Table; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | 9 | @Value 10 | @GlobalIndex(name = "key_index", fields = {"keyId"}) 11 | @GlobalIndex(name = "value_index", fields = {"valueId", "valueId2"}) 12 | @GlobalIndex(name = "key2_index", fields = {"keyId", "valueId2"}) 13 | @Table(name = "new_table_with_indexes") 14 | public class IndexedEntityNew implements Entity { 15 | @Column(name = "version_id") 16 | Id id; 17 | @Column(name = "key_id") 18 | String keyId; 19 | @Column(name = "value_id") 20 | String valueId; 21 | @Column 22 | String valueId2; 23 | 24 | @Value 25 | public static class Id implements Entity.Id { 26 | @Column(name = "version_id") 27 | String versionId; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/query/YdbTableQueryBuilderIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.query; 2 | 3 | import org.junit.ClassRule; 4 | import tech.ydb.yoj.repository.db.Repository; 5 | import tech.ydb.yoj.repository.test.TableQueryBuilderTest; 6 | import tech.ydb.yoj.repository.ydb.TestYdbRepository; 7 | import tech.ydb.yoj.repository.ydb.YdbEnvAndTransportRule; 8 | 9 | public class YdbTableQueryBuilderIntegrationTest extends TableQueryBuilderTest { 10 | @ClassRule 11 | public static final YdbEnvAndTransportRule ydbEnvAndTransport = new YdbEnvAndTransportRule(); 12 | 13 | @Override 14 | protected Repository createTestRepository() { 15 | return new TestYdbRepository(ydbEnvAndTransport.getYdbConfig(), ydbEnvAndTransport.getGrpcTransport()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/sample/model/HintAutoPartitioningByLoad.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.sample.model; 2 | 3 | import lombok.Value; 4 | import lombok.With; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | import tech.ydb.yoj.repository.ydb.client.YdbTableHint; 7 | 8 | @Value 9 | public class HintAutoPartitioningByLoad implements Entity { 10 | private static YdbTableHint ydbTableHint = YdbTableHint.autoSplitByLoad(1); 11 | 12 | Id id; 13 | 14 | @With 15 | String name; 16 | 17 | @Value 18 | public static class Id implements Entity.Id { 19 | long value; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/sample/model/HintInt64Range.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.sample.model; 2 | 3 | import lombok.Value; 4 | import lombok.With; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | import tech.ydb.yoj.repository.ydb.client.YdbTableHint; 7 | 8 | @Value 9 | public class HintInt64Range implements Entity { 10 | private static YdbTableHint ydbTableHint = YdbTableHint.int64Range(1, 32); 11 | 12 | Id id; 13 | @With 14 | String name; 15 | 16 | @Value 17 | public static class Id implements Entity.Id { 18 | long value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/sample/model/HintTablePreset.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.sample.model; 2 | 3 | import lombok.Value; 4 | import lombok.With; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | import tech.ydb.yoj.repository.ydb.client.YdbTableHint; 7 | 8 | @Value 9 | public class HintTablePreset implements Entity { 10 | private static YdbTableHint ydbTableHint = YdbTableHint.tablePreset(YdbTableHint.TablePreset.LOG_LZ4); 11 | 12 | Id id; 13 | @With 14 | String name; 15 | 16 | @Value 17 | public static class Id implements Entity.Id { 18 | String value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/sample/model/HintUniform.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.sample.model; 2 | 3 | import lombok.Value; 4 | import lombok.With; 5 | import tech.ydb.yoj.databind.DbType; 6 | import tech.ydb.yoj.databind.schema.Column; 7 | import tech.ydb.yoj.repository.db.Entity; 8 | import tech.ydb.yoj.repository.ydb.client.YdbTableHint; 9 | 10 | 11 | @Value 12 | public class HintUniform implements Entity { 13 | private static YdbTableHint ydbTableHint = YdbTableHint.uniform(48); 14 | 15 | Id id; 16 | @With 17 | String name; 18 | 19 | @Value 20 | public static class Id implements Entity.Id { 21 | @Column(dbType = DbType.UINT32) 22 | long value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/statement/DeleteByIdStatementIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.statement; 2 | 3 | import org.junit.Test; 4 | import tech.ydb.yoj.repository.db.EntitySchema; 5 | import tech.ydb.yoj.repository.db.TableDescriptor; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | public class DeleteByIdStatementIntegrationTest extends AbstractMultipleVarsYqlStatementIntegrationTestBase { 10 | 11 | @Test 12 | public void testDelete() { 13 | var txManager = getTxManager(); 14 | 15 | txManager.tx(() -> { 16 | var table = getTestEntityTable(); 17 | 18 | table.save(ENTITY_1); 19 | table.save(ENTITY_2); 20 | table.save(ENTITY_3); 21 | }); 22 | 23 | var beforeDelete = txManager.readOnly().run(() -> getTestEntityTable().findAll()); 24 | 25 | assertThat(beforeDelete).containsExactlyInAnyOrder(ENTITY_1, ENTITY_2, ENTITY_3); 26 | 27 | txManager.tx(() -> { 28 | var db = getTestDb(); 29 | 30 | var schema = EntitySchema.of(TestEntity.class); 31 | var tableDescriptor = TableDescriptor.from(schema); 32 | var deleteStatement = new DeleteByIdStatement<>(tableDescriptor, schema); 33 | 34 | db.pendingExecute(deleteStatement, ENTITY_1.getId()); 35 | db.pendingExecute(deleteStatement, ENTITY_3.getId()); 36 | }); 37 | 38 | var afterDelete = txManager.readOnly().run(() -> getTestEntityTable().findAll()); 39 | 40 | assertThat(afterDelete).containsExactlyInAnyOrder(ENTITY_2); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/statement/InsertYqlStatementIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.statement; 2 | 3 | import org.junit.Test; 4 | import tech.ydb.yoj.repository.db.EntitySchema; 5 | import tech.ydb.yoj.repository.db.TableDescriptor; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | public class InsertYqlStatementIntegrationTest extends AbstractMultipleVarsYqlStatementIntegrationTestBase { 10 | 11 | @Test 12 | public void testInsert() { 13 | var txManager = getTxManager(); 14 | 15 | txManager.tx(() -> getTestEntityTable().save(ENTITY_1)); 16 | 17 | var beforeInsert = txManager.readOnly().run(() -> getTestEntityTable().findAll()); 18 | 19 | assertThat(beforeInsert).containsExactlyInAnyOrder(ENTITY_1); 20 | 21 | txManager.tx(() -> { 22 | var db = getTestDb(); 23 | 24 | var schema = EntitySchema.of(TestEntity.class); 25 | var tableDescriptor = TableDescriptor.from(schema); 26 | var deleteStatement = new InsertYqlStatement<>(tableDescriptor, schema); 27 | 28 | db.pendingExecute(deleteStatement, ENTITY_2); 29 | db.pendingExecute(deleteStatement, ENTITY_3); 30 | }); 31 | 32 | var afterInsert = txManager.readOnly().run(() -> getTestEntityTable().findAll()); 33 | 34 | assertThat(afterInsert).containsExactlyInAnyOrder(ENTITY_1, ENTITY_2, ENTITY_3); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/statement/UpsertYqlStatementIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.statement; 2 | 3 | import org.junit.Test; 4 | import tech.ydb.yoj.repository.db.EntitySchema; 5 | import tech.ydb.yoj.repository.db.TableDescriptor; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | public class UpsertYqlStatementIntegrationTest extends AbstractMultipleVarsYqlStatementIntegrationTestBase { 10 | 11 | @Test 12 | public void testUpsert() { 13 | var txManager = getTxManager(); 14 | 15 | txManager.tx(() -> getTestEntityTable().save(ENTITY_1)); 16 | 17 | var beforeUpsert = txManager.readOnly().run(() -> getTestEntityTable().findAll()); 18 | 19 | assertThat(beforeUpsert).containsExactlyInAnyOrder(ENTITY_1); 20 | 21 | txManager.tx(() -> { 22 | var db = getTestDb(); 23 | 24 | var schema = EntitySchema.of(TestEntity.class); 25 | var tableDescriptor = TableDescriptor.from(schema); 26 | var deleteStatement = new UpsertYqlStatement<>(tableDescriptor, schema); 27 | 28 | db.pendingExecute(deleteStatement, ENTITY_1_1); 29 | db.pendingExecute(deleteStatement, ENTITY_2); 30 | db.pendingExecute(deleteStatement, ENTITY_3); 31 | }); 32 | 33 | var afterUpsert = txManager.readOnly().run(() -> getTestEntityTable().findAll()); 34 | 35 | assertThat(afterUpsert).containsExactlyInAnyOrder(ENTITY_1_1, ENTITY_2, ENTITY_3); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/util/RandomUtils.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.util; 2 | 3 | import java.util.Locale; 4 | import java.util.concurrent.ThreadLocalRandom; 5 | 6 | public class RandomUtils { 7 | private static final String alphanum = createAlphabet(); 8 | 9 | private static String createAlphabet() { 10 | String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 11 | return upper + upper.toLowerCase(Locale.ROOT) + "0123456789"; 12 | } 13 | 14 | public static String nextString(int length) { 15 | char[] buf = new char[length]; 16 | for (int idx = 0; idx < buf.length; ++idx) { 17 | buf[idx] = alphanum.charAt(ThreadLocalRandom.current().nextInt(alphanum.length())); 18 | } 19 | return new String(buf); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/yql/YqlListingQueryTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.ydb.yql; 2 | 3 | import lombok.Value; 4 | import org.junit.Test; 5 | import tech.ydb.yoj.databind.expression.FilterBuilder; 6 | import tech.ydb.yoj.databind.expression.FilterExpression; 7 | import tech.ydb.yoj.databind.schema.Schema; 8 | import tech.ydb.yoj.repository.db.Entity; 9 | import tech.ydb.yoj.repository.db.EntitySchema; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class YqlListingQueryTest { 14 | private static final Schema complexSchema = EntitySchema.of(ComplexObj.class); 15 | 16 | @Test 17 | public void sortSubexpressions() { 18 | FilterExpression filter = FilterBuilder.forSchema(complexSchema) 19 | .where("val1").gt(5) 20 | .and("id.timestamp").gte(100_500L) 21 | .and("id.key").eq("uzhos") 22 | .and("val2").neq("yozhos") 23 | .build(); 24 | 25 | assertThat(filter.toString()).isEqualTo("(val1 > 5) AND (id.timestamp >= 100500) AND (id.key == \"uzhos\") AND (val2 != \"yozhos\")"); 26 | assertThat(YqlListingQuery.normalize(filter).toString()).isEqualTo("(id.key == \"uzhos\") AND (id.timestamp >= 100500) AND (val1 > 5) AND (val2 != \"yozhos\")"); 27 | } 28 | 29 | @Value 30 | private static class ComplexObj implements Entity { 31 | Id id; 32 | 33 | int val1; 34 | String val2; 35 | 36 | @Value 37 | private static class Id implements Entity.Id { 38 | String key; 39 | long timestamp; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/resources/log4j2.yaml: -------------------------------------------------------------------------------- 1 | Configuration: 2 | appenders: 3 | Console: 4 | name: stdout 5 | PatternLayout: 6 | Pattern: "%d %-5level %-6X{tx} [%t] %c{1.}: %msg%n%throwable" 7 | 8 | Loggers: 9 | Root: 10 | level: debug 11 | AppenderRef: 12 | ref: stdout 13 | Logger: 14 | - name: io.grpc 15 | level: error 16 | - name: io.netty 17 | level: error 18 | - name: tech.ydb.yoj.repository.db 19 | level: debug 20 | - name: tech.ydb 21 | level: info 22 | # @see https://java.testcontainers.org/supported_docker_environment/logging_config/ 23 | - name: org.testcontainers 24 | level: info 25 | - name: tc 26 | level: info 27 | - name: com.github.dockerjava 28 | level: warn 29 | - name: com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.wire 30 | level: "off" # must be quoted, otherwise YAML parsers interpret this as a boolean value 31 | -------------------------------------------------------------------------------- /repository-ydb-v2/src/test/script/run-ydb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Script to run before starting YdbRepositoryIntegrationTest. 4 | # Starts a local instance of YDB inside a Docker container. 5 | 6 | set -e 7 | 8 | # Same as in https://ydb.tech/docs/en/quickstart, but with: 9 | # - TLS port=2136 and non-TLS port=2135 10 | # - Explicit row limit = 10_000 11 | # - Auto kill container and data after you press [Enter] 12 | # - No volumes (= no persistence) 13 | docker run -d -it --rm --name ydb-local -h localhost \ 14 | --platform linux/amd64 \ 15 | -p 2135:2135 -p 2136:2136 -p 8765:8765 \ 16 | -e GRPC_TLS_PORT=2136 -e GRPC_PORT=2135 -e MON_PORT=8765 \ 17 | -e YDB_USE_IN_MEMORY_PDISKS=true \ 18 | -e YDB_KQP_RESULT_ROWS_LIMIT=10000 \ 19 | docker.io/ydbplatform/local-ydb@sha256:79b36e76ecc8bc64e208e8949c1710c50d84cf718b575feba0522dba27b377cd \ 20 | & 21 | 22 | read -r -p "Press [Enter] key to exit..." 23 | docker kill ydb-local || true 24 | -------------------------------------------------------------------------------- /repository/BUILD: -------------------------------------------------------------------------------- 1 | load("@contrib_rules_jvm//java:defs.bzl", "java_library", "java_test_suite") 2 | 3 | java_library( 4 | name = "repository", 5 | srcs = glob(["src/main/**/*.java"]), 6 | visibility = ["//visibility:public"], 7 | deps = [ 8 | "//bom:lombok", 9 | "//databind", 10 | "//util", 11 | "@java_contribs_stable//:com_google_code_findbugs_jsr305", 12 | "@java_contribs_stable//:com_google_guava_guava", 13 | "@java_contribs_stable//:io_prometheus_simpleclient", 14 | "@java_contribs_stable//:javax_annotation_javax_annotation_api", 15 | ], 16 | ) 17 | 18 | java_test_suite( 19 | name = "repository-tests", 20 | srcs = glob(["src/test/java/**/*.java"]), 21 | package_prefixes = [".tech"], 22 | resources = ["src/test/resources/log4j2.yaml"], 23 | runner = "junit4", 24 | deps = [ 25 | "repository", 26 | "//bom:lombok", 27 | "//databind", 28 | "@java_contribs_stable//:com_fasterxml_jackson_dataformat_jackson_dataformat_yaml", 29 | "@java_contribs_stable//:com_google_guava_guava", 30 | "@java_contribs_stable//:javax_annotation_javax_annotation_api", 31 | "@java_contribs_stable//:org_apache_logging_log4j_log4j_api", 32 | "@java_contribs_stable//:org_apache_logging_log4j_log4j_core", 33 | "@java_contribs_stable//:org_apache_logging_log4j_log4j_slf4j2_impl", 34 | "@java_contribs_stable//:org_assertj_assertj_core", 35 | "@java_contribs_stable//:org_mockito_mockito_core", 36 | "@java_contribs_stable//:org_yaml_snakeyaml", 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/BaseDb.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository; 2 | 3 | import tech.ydb.yoj.repository.db.Entity; 4 | import tech.ydb.yoj.repository.db.Table; 5 | import tech.ydb.yoj.repository.db.TableDescriptor; 6 | import tech.ydb.yoj.repository.db.Tx; 7 | import tech.ydb.yoj.util.lang.Proxies; 8 | 9 | public interface BaseDb { 10 | static T current(Class type) { 11 | return Proxies.proxy(type, () -> Tx.Current.get().getRepositoryTransaction()); 12 | } 13 | 14 | > Table table(Class c); 15 | 16 | > Table table(TableDescriptor tableDescriptor); 17 | } 18 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/DbTypeQualifier.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository; 2 | 3 | import tech.ydb.yoj.repository.db.Repository; 4 | 5 | /** 6 | * Common database type qualifiers that can be used with any {@link Repository Repository} 7 | * implementation. 8 | */ 9 | public interface DbTypeQualifier { 10 | /** 11 | * Timestamp is represented as epoch in seconds. 12 | */ 13 | String SECONDS = "Seconds"; 14 | 15 | /** 16 | * Timestamp is represented as epoch in milliseconds. 17 | */ 18 | String MILLISECONDS = "Milliseconds"; 19 | 20 | /** 21 | * Serialize enum using name() 22 | */ 23 | String ENUM_NAME = "name"; 24 | 25 | /** 26 | * Serialize enum using toString() 27 | */ 28 | String ENUM_TO_STRING = "toString"; 29 | } 30 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/EntityList.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.function.Supplier; 6 | import java.util.stream.Stream; 7 | 8 | public class EntityList extends LinkedList> { 9 | public static EntityList create() { 10 | return new EntityList(); 11 | } 12 | 13 | public EntityList having(boolean condition, Supplier> supplier) { 14 | if (condition) { 15 | add(supplier.get()); 16 | } 17 | return this; 18 | } 19 | 20 | public EntityList having(boolean condition, Entity entity) { 21 | return having(condition, () -> entity); 22 | } 23 | 24 | public EntityList having(boolean condition, Stream> entities) { 25 | if (condition) { 26 | entities.forEach(this::add); 27 | } 28 | return this; 29 | } 30 | 31 | public final EntityList with(Entity... entities) { 32 | return with(List.of(entities)); 33 | } 34 | 35 | public final EntityList with(Iterable> entities) { 36 | entities.forEach(this::add); 37 | return this; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/NonTx.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | import com.google.common.base.Preconditions; 4 | import tech.ydb.yoj.util.lang.Proxies; 5 | 6 | public final class NonTx { 7 | private NonTx() { 8 | } 9 | 10 | /** 11 | * Wraps the specified object so that it cannot be invoked inside a transaction. 12 | * 13 | * @param type object type to wrap; must be an interface 14 | * @param t instance to wrap 15 | * @param instance type 16 | * @return wrapped instance that does not permit calls inside a transaction 17 | */ 18 | public static T nonTx(Class type, T t) { 19 | return Proxies.proxy(type, () -> { 20 | Preconditions.checkState(!Tx.Current.exists(), "%s cannot be invoked in transaction", 21 | t.getClass().getSimpleName()); 22 | return t; 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/RecordEntity.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | /** 4 | * Base interface for entities that are Java {@link java.lang.Record records}. 5 | *

Forwards {@link Entity#getId() Entity's getId() method} to the record's {@code id()} accessor. 6 | * 7 | * @param entity type 8 | */ 9 | public interface RecordEntity> extends Entity, Table.RecordViewId { 10 | @Override 11 | default Id getId() { 12 | return id(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/Repository.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | import java.util.Set; 4 | 5 | public interface Repository { 6 | default void createTablespace() { 7 | } 8 | 9 | default void checkDataCompatibility() { 10 | } 11 | 12 | default void checkSchemaCompatibility() { 13 | } 14 | 15 | default > SchemaOperations schema(Class c) { 16 | return schema(TableDescriptor.from(EntitySchema.of(c))); 17 | } 18 | 19 | > SchemaOperations schema(TableDescriptor c); 20 | 21 | /** 22 | * @deprecated For testing purposes only. Will only reliably work for tables that were created or inspected 23 | * using calls to {@link #schema(Class)}. 24 | */ 25 | @Deprecated 26 | Set> tables(); 27 | 28 | default RepositoryTransaction startTransaction() { 29 | return startTransaction(IsolationLevel.SERIALIZABLE_READ_WRITE); 30 | } 31 | 32 | default RepositoryTransaction startTransaction(IsolationLevel isolationLevel) { 33 | return startTransaction(TxOptions.create(isolationLevel)); 34 | } 35 | 36 | RepositoryTransaction startTransaction(TxOptions options); 37 | 38 | void dropDb(); 39 | 40 | String makeSnapshot(); 41 | 42 | void loadSnapshot(String id); 43 | 44 | default boolean healthCheck() { 45 | return true; 46 | } 47 | 48 | default void shutdown() { 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/SchemaOperations.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | public interface SchemaOperations { 4 | void create(); 5 | 6 | /** 7 | * Drops the table. Does nothing if the table does not {@link #exists() exist}. 8 | */ 9 | void drop(); 10 | 11 | boolean exists(); 12 | } 13 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/TableDescriptor.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | public record TableDescriptor>( 4 | Class entityType, 5 | String tableName 6 | ) { 7 | public static > TableDescriptor from(EntitySchema schema) { 8 | return new TableDescriptor<>(schema.getType(), schema.getName()); 9 | } 10 | 11 | public String toDebugString() { 12 | String entityName = entityType.getSimpleName(); 13 | if (entityName.equals(tableName)) { 14 | return entityName; 15 | } 16 | return "%s[%s]".formatted(entityName, tableName); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/Tx.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | import com.google.common.base.Preconditions; 4 | 5 | import javax.annotation.Nullable; 6 | import java.util.function.Supplier; 7 | 8 | public interface Tx { 9 | void defer(Runnable runnable); 10 | 11 | void deferFinally(Runnable runnable); 12 | 13 | void deferBeforeCommit(Runnable runnable); 14 | 15 | String getName(); 16 | 17 | RepositoryTransaction getRepositoryTransaction(); 18 | 19 | class Current { 20 | private static final ThreadLocal current = new ThreadLocal<>(); 21 | 22 | public static boolean exists() { 23 | return null != current.get(); 24 | } 25 | 26 | public static Tx get() throws IllegalStateException { 27 | Tx ctx = current.get(); 28 | if (ctx == null) { 29 | throw new IllegalStateException("Operation is not allowed out of transaction context"); 30 | } 31 | return ctx; 32 | } 33 | 34 | static R runInTx(Tx tx, Supplier supplier) { 35 | Tx existing = current.get(); 36 | current.set(tx); 37 | try { 38 | return supplier.get(); 39 | } finally { 40 | current.set(existing); 41 | } 42 | } 43 | } 44 | 45 | static void checkSameTx(@Nullable Tx originTx) { 46 | if (originTx != null && Current.exists()) { 47 | Preconditions.checkState(originTx == Current.get(), "Can't call table from another transaction"); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/TxManagerState.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | import lombok.NonNull; 4 | 5 | import javax.annotation.Nullable; 6 | 7 | public interface TxManagerState { 8 | @NonNull 9 | Repository getRepository(); 10 | 11 | @Nullable 12 | String getLogContext(); 13 | 14 | @Nullable 15 | IsolationLevel getIsolationLevel(); 16 | 17 | boolean isReadOnly(); 18 | 19 | boolean isScan(); 20 | 21 | boolean isFirstLevelCache(); 22 | } 23 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/ViewSchema.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | import tech.ydb.yoj.databind.schema.Schema; 4 | import tech.ydb.yoj.databind.schema.configuration.SchemaRegistry; 5 | import tech.ydb.yoj.databind.schema.configuration.SchemaRegistry.SchemaKey; 6 | import tech.ydb.yoj.databind.schema.naming.NamingStrategy; 7 | import tech.ydb.yoj.databind.schema.reflect.ReflectField; 8 | import tech.ydb.yoj.databind.schema.reflect.Reflector; 9 | 10 | public final class ViewSchema extends Schema { 11 | private ViewSchema(SchemaKey key, Reflector reflector) { 12 | super(key, reflector); 13 | } 14 | 15 | public static ViewSchema of(Class type) { 16 | return of(type, null); 17 | } 18 | 19 | public static ViewSchema of(Class type, NamingStrategy namingStrategy) { 20 | return of(SchemaRegistry.getDefault(), type, namingStrategy); 21 | } 22 | 23 | public static ViewSchema of(SchemaRegistry registry, Class type) { 24 | return of(registry, type, null); 25 | } 26 | 27 | public static ViewSchema of(SchemaRegistry registry, 28 | Class type, NamingStrategy namingStrategy) { 29 | return registry.getOrCreate(ViewSchema.class, ViewSchema::new, SchemaKey.of(type, namingStrategy)); 30 | } 31 | 32 | @Override 33 | protected boolean isFlattenable(ReflectField field) { 34 | return Entity.Id.class.isAssignableFrom(field.getType()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/bulk/BulkParams.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.bulk; 2 | 3 | import lombok.Builder; 4 | import lombok.Value; 5 | 6 | import java.time.Duration; 7 | 8 | @Value 9 | @Builder 10 | public class BulkParams { 11 | public static final BulkParams DEFAULT = BulkParams.builder().build(); 12 | 13 | @Builder.Default 14 | Duration timeout = Duration.ofSeconds(60); 15 | 16 | @Builder.Default 17 | Duration cancelAfter = null; 18 | 19 | @Builder.Default 20 | long deadlineAfter = 0; 21 | 22 | @Builder.Default 23 | String traceId = null; 24 | } 25 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/cache/EmptyFirstLevelCache.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.cache; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | 6 | import javax.annotation.Nullable; 7 | import java.util.List; 8 | import java.util.NoSuchElementException; 9 | import java.util.Optional; 10 | import java.util.function.Function; 11 | 12 | /*package*/ final class EmptyFirstLevelCache> implements FirstLevelCache { 13 | private static final FirstLevelCache INSTANCE = new EmptyFirstLevelCache<>(); 14 | 15 | @NonNull 16 | @SuppressWarnings("unchecked") 17 | public static > FirstLevelCache instance() { 18 | return (FirstLevelCache) INSTANCE; 19 | } 20 | 21 | @Nullable 22 | @Override 23 | public E get(@NonNull Entity.Id id, @NonNull Function, E> loader) { 24 | return loader.apply(id); 25 | } 26 | 27 | @NonNull 28 | @Override 29 | public Optional peek(@NonNull Entity.Id id) { 30 | throw new NoSuchElementException(); 31 | } 32 | 33 | @NonNull 34 | @Override 35 | public List snapshot() { 36 | return List.of(); 37 | } 38 | 39 | @Override 40 | public void put(@NonNull E e) { 41 | // NOOP 42 | } 43 | 44 | @Override 45 | public void putEmpty(@NonNull Entity.Id id) { 46 | // NOOP 47 | } 48 | 49 | @Override 50 | public boolean containsKey(@NonNull Entity.Id id) { 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/cache/EmptyRepositoryCache.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.cache; 2 | 3 | import java.util.Optional; 4 | 5 | /*package*/ class EmptyRepositoryCache implements RepositoryCache { 6 | static final RepositoryCache INSTANCE = new EmptyRepositoryCache(); 7 | 8 | @Override 9 | public boolean contains(Key key) { 10 | return false; 11 | } 12 | 13 | @Override 14 | public Optional get(Key key) { 15 | return Optional.empty(); 16 | } 17 | 18 | @Override 19 | public void put(Key key, Object value) { 20 | // intentional no-op 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/cache/FirstLevelCacheProvider.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.cache; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | import tech.ydb.yoj.repository.db.TableDescriptor; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.function.Supplier; 10 | 11 | /*package*/ final class FirstLevelCacheProvider { 12 | private final Map, FirstLevelCache> caches = new HashMap<>(); 13 | private final Supplier> cacheCreator; 14 | 15 | /*package*/ FirstLevelCacheProvider(@NonNull Supplier> cacheCreator) { 16 | this.cacheCreator = cacheCreator; 17 | } 18 | 19 | @SuppressWarnings("unchecked") 20 | public > FirstLevelCache getOrCreate(@NonNull TableDescriptor descriptor) { 21 | return (FirstLevelCache) caches.computeIfAbsent(descriptor, __ -> cacheCreator.get()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/cache/RepositoryCacheImpl.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.cache; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | /*package*/ class RepositoryCacheImpl implements RepositoryCache { 8 | private final Map> cache = new HashMap<>(); 9 | 10 | @Override 11 | public boolean contains(Key key) { 12 | return cache.containsKey(key); 13 | } 14 | 15 | @Override 16 | public Optional get(Key key) { 17 | return cache.getOrDefault(key, Optional.empty()); 18 | } 19 | 20 | @Override 21 | public void put(Key key, Object value) { 22 | cache.put(key, Optional.ofNullable(value)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/AggregateRepositoryException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.collect.ImmutableList; 5 | import lombok.Getter; 6 | 7 | import java.util.Collection; 8 | import java.util.List; 9 | 10 | import static java.util.stream.Collectors.joining; 11 | 12 | public class AggregateRepositoryException extends RepositoryException { 13 | @Getter 14 | private final List causes; 15 | 16 | private AggregateRepositoryException(Collection causes) { 17 | super("Encountered " + causes.size() + " repository exception(s): " + causes 18 | .stream() 19 | .map(ex -> String.format("\"%s\"", ex)) 20 | .collect(joining(", "))); 21 | Preconditions.checkArgument(!causes.isEmpty(), "cannot throw aggregate exception without causes"); 22 | this.causes = ImmutableList.copyOf(causes); 23 | 24 | this.causes.forEach(this::addSuppressed); 25 | } 26 | 27 | public static void throwIfNeeded(Collection causes) 28 | throws RepositoryException { 29 | if (causes.isEmpty()) { 30 | return; 31 | } 32 | if (causes.size() == 1) { 33 | throw causes.iterator().next(); 34 | } 35 | throw new AggregateRepositoryException(causes); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/ConversionException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | /** 4 | * Thrown when the repository cannot convert a raw database row to entity, or vice versa. 5 | */ 6 | public class ConversionException extends RepositoryException { 7 | public ConversionException(String message) { 8 | super(message); 9 | } 10 | 11 | public ConversionException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/CreateTableException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | public class CreateTableException extends RepositoryException { 4 | public CreateTableException(String msg) { 5 | super(msg); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/DeadlineExceededException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | public class DeadlineExceededException extends RepositoryException { 4 | public DeadlineExceededException(String message) { 5 | super(message); 6 | } 7 | 8 | public DeadlineExceededException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/DropTableException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | public class DropTableException extends RepositoryException { 4 | public DropTableException(String msg) { 5 | super(msg); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/EntityAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | public class EntityAlreadyExistsException extends RetryableException { 4 | public EntityAlreadyExistsException(String message) { 5 | super(message); 6 | } 7 | 8 | @Override 9 | public RepositoryException rethrow() { 10 | return this; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/IllegalTransactionException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | public class IllegalTransactionException extends RepositoryException { 4 | public IllegalTransactionException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/IllegalTransactionIsolationLevelException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | import tech.ydb.yoj.repository.db.IsolationLevel; 4 | 5 | import static java.lang.String.format; 6 | 7 | public class IllegalTransactionIsolationLevelException extends IllegalTransactionException { 8 | public IllegalTransactionIsolationLevelException(String message, IsolationLevel isolationLevel) { 9 | super(format("%s are not allowed for isolation level %s", message, isolationLevel)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/IllegalTransactionScanException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | import static java.lang.String.format; 4 | 5 | public class IllegalTransactionScanException extends IllegalTransactionException { 6 | public IllegalTransactionScanException(String message) { 7 | super(format("%s are not allowed in scan transaction", message)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/InternalRepositoryException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | public class InternalRepositoryException extends RepositoryException { 4 | public InternalRepositoryException(Throwable cause) { 5 | super("Unhandled repository exception", cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/OptimisticLockException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | public class OptimisticLockException extends RetryableException { 4 | public OptimisticLockException(String message) { 5 | super(message); 6 | } 7 | 8 | public OptimisticLockException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/QueryCancelledException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | public class QueryCancelledException extends RepositoryException { 4 | public QueryCancelledException(String message) { 5 | super(message); 6 | } 7 | 8 | public QueryCancelledException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/QueryInterruptedException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | /** 4 | * Thrown if the thread awaiting the query's results has been interrupted. 5 | */ 6 | public class QueryInterruptedException extends RepositoryException { 7 | public QueryInterruptedException(String message) { 8 | super(message); 9 | } 10 | 11 | public QueryInterruptedException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/RepositoryException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | /** 4 | * Base class for all database access exceptions. Instances of this class are treated as non-retryable by default, 5 | * unless they are subclasses of {@link RetryableException}. 6 | */ 7 | public abstract class RepositoryException extends RuntimeException { 8 | public RepositoryException(String message) { 9 | super(message); 10 | } 11 | 12 | public RepositoryException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/RetryableException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | import tech.ydb.yoj.util.retry.RetryPolicy; 4 | 5 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 6 | 7 | /** 8 | * Base class for retryable database access exceptions. 9 | */ 10 | public abstract class RetryableException extends RepositoryException { 11 | private final RetryPolicy retryPolicy; 12 | 13 | protected RetryableException(String message, RetryPolicy retryPolicy, Throwable cause) { 14 | super(message, cause); 15 | this.retryPolicy = retryPolicy; 16 | } 17 | 18 | protected RetryableException(String message, RetryPolicy retryPolicy) { 19 | super(message); 20 | this.retryPolicy = retryPolicy; 21 | } 22 | 23 | protected RetryableException(String message, Throwable cause) { 24 | this(message, RetryPolicy.retryImmediately(), cause); 25 | } 26 | 27 | protected RetryableException(String message) { 28 | this(message, RetryPolicy.retryImmediately()); 29 | } 30 | 31 | /** 32 | * Sleeps for the recommended amount of time before retrying. 33 | * 34 | * @param attempt request attempt count (starting from 1) 35 | */ 36 | public void sleep(int attempt) { 37 | try { 38 | MILLISECONDS.sleep(retryPolicy.calcDuration(attempt).toMillis()); 39 | } catch (InterruptedException e) { 40 | Thread.currentThread().interrupt(); 41 | throw new QueryInterruptedException("DB query interrupted", e); 42 | } 43 | } 44 | 45 | public RepositoryException rethrow() { 46 | return UnavailableException.afterRetries("Retries failed", this); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/exception/UnavailableException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.exception; 2 | 3 | import lombok.Getter; 4 | 5 | public class UnavailableException extends RepositoryException { 6 | @Getter 7 | public final boolean alreadyRetried; 8 | 9 | public UnavailableException(String message) { 10 | super(message); 11 | this.alreadyRetried = false; 12 | } 13 | 14 | public UnavailableException(String message, Throwable cause) { 15 | this(message, cause, false); 16 | } 17 | 18 | public UnavailableException(String message, Throwable cause, boolean alreadyRetried) { 19 | super(message, cause); 20 | this.alreadyRetried = alreadyRetried; 21 | } 22 | 23 | public static UnavailableException afterRetries(String message, Throwable cause) { 24 | return new UnavailableException(message, cause, true); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/list/BadListingException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.list; 2 | 3 | import lombok.Getter; 4 | 5 | public abstract class BadListingException extends IllegalArgumentException { 6 | protected BadListingException(String message) { 7 | super(message); 8 | } 9 | 10 | protected BadListingException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | 14 | @Getter 15 | public static final class BadPageSize extends BadListingException { 16 | private final long pageSize; 17 | private final long maxPageSize; 18 | 19 | public BadPageSize(long pageSize, long maxPageSize) { 20 | super("Invalid page size (%d). Must be between and 1 and %d, inclusive".formatted(pageSize, maxPageSize)); 21 | this.pageSize = pageSize; 22 | this.maxPageSize = maxPageSize; 23 | } 24 | } 25 | 26 | @Getter 27 | public static final class BadOffset extends BadListingException { 28 | private final long maxSkipSize; 29 | 30 | public BadOffset(long maxSkipSize) { 31 | super("Invalid page token. Paging more than %d results in total is not supported".formatted(maxSkipSize)); 32 | this.maxSkipSize = maxSkipSize; 33 | } 34 | } 35 | 36 | public static final class InvalidPageToken extends BadListingException { 37 | public InvalidPageToken() { 38 | this(null); 39 | } 40 | 41 | public InvalidPageToken(Throwable cause) { 42 | super("Invalid page token", cause); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/list/token/EmptyPageToken.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.list.token; 2 | 3 | import lombok.NonNull; 4 | import tech.ydb.yoj.repository.db.list.BadListingException.InvalidPageToken; 5 | import tech.ydb.yoj.repository.db.list.GenericListResult; 6 | import tech.ydb.yoj.repository.db.list.ListRequest; 7 | 8 | import javax.annotation.Nullable; 9 | 10 | /** 11 | * The most trivial implementation of {@link PageToken}: it does not produce page tokens, and is thus only suitable when 12 | * the listing produces at most one page of results. 13 | */ 14 | public final class EmptyPageToken implements PageToken { 15 | public static final PageToken INSTANCE = new EmptyPageToken(); 16 | 17 | @Nullable 18 | @Override 19 | public String encode(@NonNull GenericListResult result) { 20 | return null; 21 | } 22 | 23 | @NonNull 24 | @Override 25 | public ListRequest.Builder decode(@NonNull ListRequest.Builder bldr, 26 | @NonNull String token) throws InvalidPageToken { 27 | throw new InvalidPageToken(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/projection/ProjectionCache.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.projection; 2 | 3 | 4 | import tech.ydb.yoj.InternalApi; 5 | import tech.ydb.yoj.repository.db.Entity; 6 | import tech.ydb.yoj.repository.db.RepositoryTransaction; 7 | 8 | @InternalApi 9 | public interface ProjectionCache { 10 | void load(Entity entity); 11 | 12 | void save(Entity entity); 13 | 14 | void delete(Entity.Id id); 15 | 16 | void applyProjectionChanges(RepositoryTransaction transaction); 17 | } 18 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/projection/Projections.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.projection; 2 | 3 | import tech.ydb.yoj.repository.db.Entity; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface Projections { 13 | Class>[] value(); 14 | } 15 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/projection/RoProjectionCache.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.projection; 2 | 3 | import tech.ydb.yoj.InternalApi; 4 | import tech.ydb.yoj.repository.db.Entity; 5 | import tech.ydb.yoj.repository.db.RepositoryTransaction; 6 | 7 | @InternalApi 8 | public class RoProjectionCache implements ProjectionCache { 9 | @Override 10 | public void load(Entity entity) { 11 | } 12 | 13 | @Override 14 | public void save(Entity entity) { 15 | throw new UnsupportedOperationException("Should not be invoked in RO"); 16 | } 17 | 18 | @Override 19 | public void delete(Entity.Id id) { 20 | throw new UnsupportedOperationException("Should not be invoked in RO"); 21 | } 22 | 23 | @Override 24 | public void applyProjectionChanges(RepositoryTransaction transaction) { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /repository/src/main/java/tech/ydb/yoj/repository/db/statement/Changeset.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db.statement; 2 | 3 | import lombok.NonNull; 4 | 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * @deprecated Blindly setting entity fields is not recommended. Use {@code Table.modifyIfPresent()} instead, unless you 10 | * have specific requirements. 11 | *

Blind updates disrupt query merging mechanism, so you typically won't able to run multiple blind update statements 12 | * in the same transaction, or interleave them with upserts ({@code Table.save()}) and inserts. 13 | *

Blind updates also do not update projections because they do not load the entity before performing the update; 14 | * this can cause projections to be inconsistent with the main entity. 15 | */ 16 | @Deprecated 17 | public final class Changeset { 18 | private final Map newValues = new LinkedHashMap<>(); 19 | 20 | public Changeset() { 21 | } 22 | 23 | public static Changeset setField(String fieldPath, V value) { 24 | return new Changeset().set(fieldPath, value); 25 | } 26 | 27 | public Changeset set(@NonNull String fieldPath, T value) { 28 | this.newValues.put(fieldPath, value); 29 | return this; 30 | } 31 | 32 | public Changeset setAll(@NonNull Changeset other) { 33 | return setAll(other.newValues); 34 | } 35 | 36 | public Changeset setAll(@NonNull Map fieldValues) { 37 | this.newValues.putAll(fieldValues); 38 | return this; 39 | } 40 | 41 | public Map toMap() { 42 | return new LinkedHashMap<>(this.newValues); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /repository/src/test/java/tech/ydb/yoj/repository/db/PojoEntityTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | import lombok.NonNull; 4 | import lombok.Value; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertFalse; 8 | import static org.junit.Assert.assertTrue; 9 | 10 | public class PojoEntityTest { 11 | 12 | @Value 13 | private static class Ent implements Entity { 14 | @NonNull 15 | Id id; 16 | int payload; 17 | 18 | @Value 19 | private static class Id implements Entity.Id { 20 | String part1; 21 | String part2; 22 | } 23 | } 24 | 25 | @Test 26 | public void testPartialId() { 27 | var completeId = new Ent.Id("a", "b"); 28 | var partialId = new Ent.Id("a", null); 29 | 30 | assertTrue(partialId.isPartial()); 31 | assertFalse(completeId.isPartial()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /repository/src/test/java/tech/ydb/yoj/repository/db/RecordEntityTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.db; 2 | 3 | import lombok.NonNull; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | public class RecordEntityTest { 10 | private record Ent(@NonNull Id id, int payload) implements RecordEntity { 11 | private record Id(String part1, String parts) implements Entity.Id { 12 | } 13 | } 14 | 15 | @Test 16 | public void testPartialId() { 17 | var completeId = new Ent.Id("a", "b"); 18 | var partialId = new Ent.Id("a", null); 19 | 20 | assertTrue(partialId.isPartial()); 21 | assertFalse(completeId.isPartial()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /repository/src/test/java/tech/ydb/yoj/repository/hybrid/AccountSnapshotMetadata.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.hybrid; 2 | 3 | import java.time.Instant; 4 | 5 | public record AccountSnapshotMetadata(Instant at, String description) { 6 | } 7 | -------------------------------------------------------------------------------- /repository/src/test/java/tech/ydb/yoj/repository/hybrid/JobArgs.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.hybrid; 2 | 3 | public record JobArgs(Object request) { 4 | } 5 | -------------------------------------------------------------------------------- /repository/src/test/kotlin/tech/ydb/yoj/repository/hybrid/Account.kt: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.hybrid 2 | 3 | import tech.ydb.yoj.repository.db.Entity 4 | 5 | data class Account( 6 | private val id: Id, 7 | private val version: Long, 8 | val login: String, 9 | ) : Entity { 10 | override fun getId() = id 11 | 12 | data class Id(val value: String) : Entity.Id 13 | 14 | data class Snapshot( 15 | private val id: Id, 16 | private val account: Account?, 17 | val meta: AccountSnapshotMetadata 18 | ) : Entity { 19 | override fun getId() = id 20 | 21 | data class Id( 22 | private val id: Account.Id, 23 | private val version: Long 24 | ) : Entity.Id 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /repository/src/test/kotlin/tech/ydb/yoj/repository/hybrid/Job.kt: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.repository.hybrid 2 | 3 | import tech.ydb.yoj.databind.schema.Column 4 | import tech.ydb.yoj.repository.db.Entity 5 | 6 | data class Job( 7 | private val id: Id, 8 | @Column(flatten = false) val args: JobArgs?, 9 | ) : Entity { 10 | override fun getId() = id 11 | 12 | data class Id(val value: String) : Entity.Id 13 | } 14 | -------------------------------------------------------------------------------- /repository/src/test/resources/log4j2.yaml: -------------------------------------------------------------------------------- 1 | Configuration: 2 | appenders: 3 | Console: 4 | name: stdout 5 | PatternLayout: 6 | Pattern: "%d %-5level %-6X{tx} [%t] %c{1.}: %msg%n%throwable" 7 | 8 | Loggers: 9 | Root: 10 | level: info 11 | AppenderRef: 12 | ref: stdout 13 | Logger: 14 | - name: tech.ydb.yoj.repository.db 15 | level: debug 16 | -------------------------------------------------------------------------------- /util/BUILD: -------------------------------------------------------------------------------- 1 | load("@contrib_rules_jvm//java:defs.bzl", "java_library", "java_test_suite") 2 | 3 | java_library( 4 | name = "util", 5 | srcs = glob(["src/main/**/*.java"]), 6 | visibility = ["//visibility:public"], 7 | deps = [ 8 | "//bom:lombok", 9 | "@java_contribs_stable//:com_google_code_findbugs_jsr305", 10 | "@java_contribs_stable//:com_google_guava_guava", 11 | "@java_contribs_stable//:javax_annotation_javax_annotation_api", 12 | "@java_contribs_stable//:org_slf4j_slf4j_api", 13 | ], 14 | ) 15 | 16 | java_test_suite( 17 | name = "util-test", 18 | srcs = glob(["src/test/java/**/*.java"]), 19 | package_prefixes = [".tech"], 20 | runner = "junit4", 21 | visibility = ["//visibility:public"], 22 | deps = [ 23 | ":util", 24 | "//bom:lombok", 25 | "@java_contribs_stable//:junit_junit", 26 | "@java_contribs_stable//:org_apache_logging_log4j_log4j_slf4j2_impl", 27 | "@java_contribs_stable//:org_assertj_assertj_core", 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | yoj-util 8 | jar 9 | 10 | 11 | tech.ydb.yoj 12 | yoj-parent 13 | 2.6.19-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | YOJ - Utilities 18 | 19 | Utility classes used in YOJ (YDB ORM for Java) implementation. 20 | 21 | 22 | 23 | 24 | com.google.guava 25 | guava 26 | 27 | 28 | com.google.code.findbugs 29 | jsr305 30 | 31 | 32 | javax.annotation 33 | javax.annotation-api 34 | 35 | 36 | org.slf4j 37 | slf4j-api 38 | 39 | 40 | 41 | org.apache.logging.log4j 42 | log4j-slf4j-impl 43 | test 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /util/src/main/java/tech/ydb/yoj/util/lang/Proxies.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.util.lang; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.util.function.Supplier; 5 | 6 | import static java.lang.reflect.Proxy.newProxyInstance; 7 | 8 | public final class Proxies { 9 | private Proxies() { 10 | } 11 | 12 | @SuppressWarnings("unchecked") 13 | public static T proxy(Class type, Supplier target) { 14 | return (T) newProxyInstance(type.getClassLoader(), new Class[]{type}, (__, method, args) -> { 15 | try { 16 | return method.invoke(target.get(), args); 17 | } catch (InvocationTargetException e) { 18 | throw e.getCause(); 19 | } 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /util/src/main/java/tech/ydb/yoj/util/lang/Strings.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.util.lang; 2 | 3 | import lombok.NonNull; 4 | 5 | import javax.annotation.Nullable; 6 | import java.util.Objects; 7 | import java.util.stream.Stream; 8 | 9 | import static java.util.stream.Collectors.joining; 10 | 11 | public final class Strings { 12 | private Strings() { 13 | } 14 | 15 | public static String join(String delimiter, Object... values) { 16 | return Stream.of(values) 17 | .filter(Objects::nonNull) 18 | .map(Object::toString) 19 | .filter(s -> !s.isBlank()) 20 | .collect(joining(delimiter)); 21 | } 22 | 23 | public static String removeSuffix(@Nullable String s, @NonNull String suffix) { 24 | return s != null && s.endsWith(suffix) ? s.substring(0, s.length() - suffix.length()) : s; 25 | } 26 | 27 | public static String leftPad(@NonNull String s, int minLength, char padChar) { 28 | return s.length() >= minLength ? s : String.valueOf(padChar).repeat(minLength - s.length()) + s; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /util/src/main/java/tech/ydb/yoj/util/lang/UncheckedInterruptedException.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.util.lang; 2 | 3 | public class UncheckedInterruptedException extends RuntimeException { 4 | public UncheckedInterruptedException(InterruptedException e) { 5 | super(e); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /util/src/main/java/tech/ydb/yoj/util/retry/FixedDelayRetryPolicy.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.util.retry; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.Value; 5 | 6 | import javax.annotation.Nullable; 7 | import java.time.Duration; 8 | 9 | import static lombok.AccessLevel.PACKAGE; 10 | 11 | @Value 12 | @RequiredArgsConstructor(access = PACKAGE) 13 | public class FixedDelayRetryPolicy implements RetryPolicy { 14 | long delay; 15 | double jitter; 16 | 17 | @Override 18 | public Duration calcDuration(int attempt) { 19 | return Duration.ofMillis((long) (delay * (1 + jitter * (Math.random() * 2 - 1)))); 20 | } 21 | 22 | @Override 23 | public boolean isSameAs(@Nullable RetryPolicy other) { 24 | return this.equals(other); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /util/src/main/java/tech/ydb/yoj/util/retry/RetryPolicy.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.util.retry; 2 | 3 | import lombok.NonNull; 4 | 5 | import java.time.Duration; 6 | 7 | public interface RetryPolicy { 8 | /** 9 | * @param attempt failed attempt number, counting from 1 10 | * @return recommended retry interval 11 | */ 12 | Duration calcDuration(int attempt); 13 | 14 | boolean isSameAs(@NonNull RetryPolicy other); 15 | 16 | static RetryPolicy expBackoff() { 17 | return ExponentialBackoffRetryPolicy.DEFAULT; 18 | } 19 | 20 | @NonNull 21 | static RetryPolicy expBackoff(long initial, long max, double jitter, double multiplier) { 22 | return new ExponentialBackoffRetryPolicy(initial, max, jitter, multiplier); 23 | } 24 | 25 | @NonNull 26 | static RetryPolicy expBackoff(Duration initial, Duration max, double jitter, double multiplier) { 27 | return new ExponentialBackoffRetryPolicy(initial.toMillis(), max.toMillis(), jitter, multiplier); 28 | } 29 | 30 | @NonNull 31 | static RetryPolicy fixed(Duration delay) { 32 | return fixed(delay.toMillis()); 33 | } 34 | 35 | @NonNull 36 | static RetryPolicy fixed(Duration delay, double jitter) { 37 | return fixed(delay.toMillis(), jitter); 38 | } 39 | 40 | @NonNull 41 | static RetryPolicy fixed(long delay) { 42 | return fixed(delay, 0); 43 | } 44 | 45 | @NonNull 46 | static RetryPolicy fixed(long delay, double jitter) { 47 | return new FixedDelayRetryPolicy(delay, jitter); 48 | } 49 | 50 | static RetryPolicy retryImmediately() { 51 | return new FixedDelayRetryPolicy(0L, 0.0); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /util/src/test/java/tech/ydb/yoj/util/lang/StringsTest.java: -------------------------------------------------------------------------------- 1 | package tech.ydb.yoj.util.lang; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 7 | 8 | public class StringsTest { 9 | @Test 10 | public void join() { 11 | assertThat(Strings.join("?", "url/path", null)).isEqualTo("url/path"); 12 | assertThat(Strings.join("?", "url/path", "")).isEqualTo("url/path"); 13 | assertThat(Strings.join("?", "url/path", "a=1&b=2")).isEqualTo("url/path?a=1&b=2"); 14 | } 15 | 16 | @Test 17 | public void testRemoveSuffix() { 18 | assertThat(Strings.removeSuffix("abc", "")).isEqualTo("abc"); 19 | assertThat(Strings.removeSuffix("abc", "de")).isEqualTo("abc"); 20 | assertThat(Strings.removeSuffix("abc", "bc")).isEqualTo("a"); 21 | assertThat(Strings.removeSuffix("abc", "abc")).isEmpty(); 22 | assertThat(Strings.removeSuffix("HelloWorldException", "Exception")).isEqualTo("HelloWorld"); 23 | assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> Strings.removeSuffix("abc", null)); 24 | } 25 | 26 | @Test 27 | public void testLeftPad() { 28 | assertThat(Strings.leftPad("3", 3, '0')).isEqualTo("003"); 29 | assertThat(Strings.leftPad("003", 3, '0')).isEqualTo("003"); 30 | assertThat(Strings.leftPad("0003", 3, '0')).isEqualTo("0003"); 31 | assertThat(Strings.leftPad(" 45", 6, ' ')).isEqualTo(" 45"); 32 | } 33 | } 34 | --------------------------------------------------------------------------------