Merge "Add new room external antlr library as dependency to plugin and itg test" into androidx-main
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java
index 4af8c7b..43242b4 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java
@@ -22,8 +22,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.camera.core.CameraSelector;
+import androidx.camera.core.ExperimentalLensFacing;
import androidx.camera.core.Logger;
/**
@@ -31,11 +33,15 @@
* b/167201193.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@OptIn(markerClass = ExperimentalLensFacing.class)
public final class CameraValidator {
private CameraValidator() {
}
private static final String TAG = "CameraValidator";
+ private static final CameraSelector EXTERNAL_LENS_FACING =
+ new CameraSelector.Builder().requireLensFacing(
+ CameraSelector.LENS_FACING_EXTERNAL).build();
/**
* Verifies the initialized camera instance in the CameraRepository
@@ -104,6 +110,13 @@
Logger.w(TAG, "Camera LENS_FACING_FRONT verification failed", e);
exception = e;
}
+ try {
+ // Verifies the EXTERNAL camera.
+ EXTERNAL_LENS_FACING.select(cameraRepository.getCameras());
+ availableCameraCount++;
+ } catch (IllegalArgumentException e) {
+ Logger.d(TAG, "No camera with LENS_FACING_EXTERNAL characteristic detected", e);
+ }
if (exception != null) {
Logger.e(TAG, "Camera LensFacing verification failed, existing cameras: "
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
index 5434623..324e4b7 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
@@ -48,6 +48,7 @@
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.platform.isJs
+import org.jetbrains.kotlin.platform.isWasm
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.platform.konan.isNative
@@ -255,7 +256,7 @@
).lower(moduleFragment)
}
- if (pluginContext.platform.isJs()) {
+ if (pluginContext.platform.isJs() || pluginContext.platform.isWasm()) {
WrapJsComposableLambdaLowering(
pluginContext,
symbolRemapper,
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index fddc4b1..3d408cf 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -98,6 +98,7 @@
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.isJs
+import org.jetbrains.kotlin.platform.isWasm
import org.jetbrains.kotlin.platform.jvm.isJvm
private class CaptureCollector {
@@ -869,24 +870,23 @@
val function = expression.function
val argumentCount = function.valueParameters.size
- val isJs = context.platform.isJs()
- if (argumentCount > MAX_RESTART_ARGUMENT_COUNT && isJs) {
+ if (argumentCount > MAX_RESTART_ARGUMENT_COUNT && !context.platform.isJvm()) {
error(
"only $MAX_RESTART_ARGUMENT_COUNT parameters " +
- "in @Composable lambda are supported on JS"
+ "in @Composable lambda are supported on" +
+ "non-JVM targets (K/JS or K/Wasm or K/Native)"
)
}
val useComposableLambdaN = argumentCount > MAX_RESTART_ARGUMENT_COUNT
val useComposableFactory = collector.hasCaptures && declarationContext.composable
- val rememberComposableN = rememberComposableLambdaNFunction ?: composableLambdaNFunction
val rememberComposable = rememberComposableLambdaFunction ?: composableLambdaFunction
val requiresExplicitComposerParameter = useComposableFactory &&
rememberComposableLambdaFunction == null
val restartFactorySymbol =
if (useComposableFactory)
if (useComposableLambdaN)
- rememberComposableN
+ rememberComposableLambdaNFunction ?: composableLambdaNFunction
else rememberComposable
else if (useComposableLambdaN)
composableLambdaInstanceNFunction
@@ -952,7 +952,7 @@
): IrExpression {
// Kotlin/JS doesn't have an optimization for non-capturing lambdas
// https://youtrack.jetbrains.com/issue/KT-49923
- val skipNonCapturingLambdas = !context.platform.isJs()
+ val skipNonCapturingLambdas = !context.platform.isJs() && !context.platform.isWasm()
// If the function doesn't capture, Kotlin's default optimization is sufficient
if (captures.isEmpty() && skipNonCapturingLambdas) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt
index 64a3e60..9be135a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt
@@ -88,7 +88,7 @@
}
@Deprecated(
- "Please use the new constructor that takes optional autoCorrect parameter.",
+ "Please use the new constructor that takes optional autoCorrectEnabled parameter.",
level = DeprecationLevel.WARNING,
replaceWith = ReplaceWith(
"KeyboardOptions(" +
@@ -175,7 +175,7 @@
)
@Deprecated(
- "Please use the autoCorrectMode property.",
+ "Please use the autoCorrectEnabled property.",
level = DeprecationLevel.WARNING
)
val autoCorrect: Boolean get() = autoCorrectOrDefault
@@ -253,8 +253,8 @@
}
@Deprecated(
- "Please use the copy function that takes an autoCorrectMode parameter.",
- level = DeprecationLevel.WARNING,
+ "Please use the copy function that takes an autoCorrectEnabled parameter.",
+ level = DeprecationLevel.HIDDEN,
replaceWith = ReplaceWith(
"copy(" +
"capitalization = capitalization, " +
diff --git a/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CompositionLocalSamples.kt b/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CompositionLocalSamples.kt
index 06e3907..915eb75 100644
--- a/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CompositionLocalSamples.kt
+++ b/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CompositionLocalSamples.kt
@@ -65,6 +65,61 @@
}
@Sampled
+fun compositionLocalComputedAfterProvidingLocal() {
+ val LocalValue = compositionLocalOf { 10 }
+ val LocalLargerValue = compositionLocalOf { 12 }
+ val LocalComputedValue = compositionLocalWithComputedDefaultOf {
+ LocalValue.currentValue + 4
+ }
+
+ // In this example `LocalLargerValue` needs to be re-provided
+ // whenever `LocalValue` is provided to keep its value larger
+ // then `LocalValue`. However, `LocalComputedValue` does not
+ // need to be re-provided to stay larger than `LocalValue` as
+ // it is calculated based on the currently provided value for
+ // `LocalValue`. Whenever `LocalValue` is provided the value
+ // of `LocalComputedValue` is computed based on the currently
+ // provided value for `LocalValue`.
+
+ @Composable
+ fun App() {
+ // Value is 10, the default value for LocalValue
+ val value = LocalValue.current
+ // Value is 12, the default value
+ val largerValue = LocalLargerValue.current
+ // Value is computed to be 14
+ val computedValue = LocalComputedValue.current
+ CompositionLocalProvider(
+ LocalValue provides 20
+ ) {
+ // Value is 20 provided above
+ val nestedValue = LocalValue.current
+ // Value is still 12 as an updated value was not re-provided
+ val nestedLargerValue = LocalLargerValue.current
+ // Values is computed to be 24; LocalValue.current + 4
+ val nestedComputedValue = LocalComputedValue.current
+ CompositionLocalProvider(
+ LocalLargerValue provides LocalValue.current + 2
+ ) {
+ // Value is 22 provided above
+ val newLargerValue = LocalLargerValue.current
+
+ CompositionLocalProvider(
+ LocalValue provides 50
+ ) {
+ // Value is now 50 provided above
+ val finalValue = LocalValue.current
+ // Value is still 22
+ val finalLargerValue = LocalLargerValue.current
+ // Value is now computed to be 54
+ val finalComputed = LocalComputedValue.current
+ }
+ }
+ }
+ }
+}
+
+@Sampled
fun someScreenSample() {
@Composable
fun SomeScreen() {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt
index 4af9eb7..5cf1202 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt
@@ -114,7 +114,16 @@
* other composition locals by calling [CompositionLocalAccessorScope.currentValue], which is an
* extension function provided by the context for a [CompositionLocal] key.
*
+ * The lambda passed to [providesComputed] will be invoked every time the
+ * [CompositionLocal.current] is evaluated for the composition local and computes its value
+ * based on the current value of the locals referenced in the lambda at the time
+ * [CompositionLocal.current] is evaluated. This allows providing values that can be derived
+ * from other locals. For example, if accent colors can be calculated from a single base color,
+ * the accent colors can be provided as computed composition locals. Providing a new base color
+ * would automatically update all the accent colors.
+ *
* @sample androidx.compose.runtime.samples.compositionLocalProvidedComputed
+ * @sample androidx.compose.runtime.samples.compositionLocalComputedAfterProvidingLocal
*
* @see CompositionLocal
* @see CompositionLocalContext
@@ -281,7 +290,16 @@
* when the value is not provided. For a [compositionLocalOf] the default value is returned. If no
* default value has be computed for [CompositionLocal] the default computation is called.
*
+ * The lambda passed to [compositionLocalWithComputedDefaultOf] will be invoked every time the
+ * [CompositionLocal.current] is evaluated for the composition local and computes its value based
+ * on the current value of the locals referenced in the lambda at the time
+ * [CompositionLocal.current] is evaluated. This allows providing values that can be derived from
+ * other locals. For example, if accent colors can be calculated from a single base color, the
+ * accent colors can be provided as computed composition locals. Providing a new base color would
+ * automatically update all the accent colors.
+ *
* @sample androidx.compose.runtime.samples.compositionLocalComputedByDefault
+ * @sample androidx.compose.runtime.samples.compositionLocalComputedAfterProvidingLocal
*
* @param defaultComputation the default computation to use when this [CompositionLocal] is not
* provided.
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
index 3e4c0db..46de688 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
@@ -774,6 +774,65 @@
}
}
}
+
+ @Suppress("LocalVariableName")
+ @Test
+ // Validate androidx.compose.runtime.samples.compositionLocalComputedAfterProvidingLocal
+ fun validateSampledExample() = compositionTest {
+ val LocalValue = compositionLocalOf { 10 }
+ val LocalLargerValue = compositionLocalOf { 12 }
+ val LocalComputedValue = compositionLocalWithComputedDefaultOf {
+ LocalValue.currentValue + 4
+ }
+
+ @Composable
+ fun App() {
+ // Value is 10, the default value for LocalValue
+ val value = LocalValue.current
+ assertEquals(10, value)
+ // Value is 12, the default value
+ val largerValue = LocalLargerValue.current
+ assertEquals(12, largerValue)
+ // Value is computed to be 14
+ val computedValue = LocalComputedValue.current
+ assertEquals(14, computedValue)
+ CompositionLocalProvider(
+ LocalValue provides 20
+ ) {
+ // Value is 20 provided above
+ val nestedValue = LocalValue.current
+ assertEquals(20, nestedValue)
+ // Value is still 12 as an updated value was not re-provided
+ val nestedLargerValue = LocalLargerValue.current
+ assertEquals(12, nestedLargerValue)
+ // Values is computed to be 24; LocalValue.current + 4
+ val nestedComputedValue = LocalComputedValue.current
+ assertEquals(24, nestedComputedValue)
+ CompositionLocalProvider(
+ LocalLargerValue provides LocalValue.current + 2
+ ) {
+ // Value is 22 provided above
+ val newLargerValue = LocalLargerValue.current
+ assertEquals(22, newLargerValue)
+ CompositionLocalProvider(
+ LocalValue provides 50
+ ) {
+ // Value is now 50 provided above
+ val finalValue = LocalValue.current
+ assertEquals(50, finalValue)
+ // Value is still 22
+ val finalLargerValue = LocalLargerValue.current
+ assertEquals(22, finalLargerValue)
+ // Value is now computed to be 54
+ val finalComputed = LocalComputedValue.current
+ assertEquals(54, finalComputed)
+ }
+ }
+ }
+ }
+
+ compose { App() }
+ }
}
val cacheLocal = staticCompositionLocalOf { "Unset" }
diff --git a/development/importMaven/src/main/kotlin/androidx/build/importMaven/ArtifactResolver.kt b/development/importMaven/src/main/kotlin/androidx/build/importMaven/ArtifactResolver.kt
index 3d98305..89f79a8 100644
--- a/development/importMaven/src/main/kotlin/androidx/build/importMaven/ArtifactResolver.kt
+++ b/development/importMaven/src/main/kotlin/androidx/build/importMaven/ArtifactResolver.kt
@@ -308,15 +308,20 @@
vararg dependencies: Dependency
): List<Configuration> {
return listOf(
- LibraryElements.JAR,
- "aar"
- ).map { libraryElement ->
+ LibraryElements.JAR to TargetJvmEnvironment.STANDARD_JVM,
+ LibraryElements.JAR to TargetJvmEnvironment.ANDROID,
+ "aar" to TargetJvmEnvironment.ANDROID,
+ ).map { (libraryElement, jvmEnvironment) ->
createConfiguration(*dependencies) {
attributes.apply {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, libraryElement)
attribute(Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME)
attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY)
attribute(Bundling.BUNDLING_ATTRIBUTE, Bundling.EXTERNAL)
+ attribute(
+ TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
+ jvmEnvironment
+ )
}
}
}
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 58ece42..6601baa 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -634,9 +634,14 @@
<sha256 value="02c12c3c2ae12dd475219ff691c82a4d9ea21f44bc594a181295bf6d43dcfbb0" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
+ <component group="com.google.guava" name="guava" version="32.1.3-jre">
+ <artifact name="guava-32.1.3-android.jar">
+ <sha256 value="20e6ac8902ddf49e7806cc70f3054c8d91accb5eefdc10f3207e80e0a336b263" reason="https://github.com/google/guava/issues/7154"/>
+ </artifact>
+ </component>
<component group="com.google.guava" name="guava" version="32.1.3-android">
<artifact name="guava-32.1.3-jre.jar">
- <sha256 value="6d4e2b5a118aab62e6e5e29d185a0224eed82c85c40ac3d33cf04a270c3b3744" origin="Generated by Gradle" reason="Artifact is not signed"/>
+ <sha256 value="6d4e2b5a118aab62e6e5e29d185a0224eed82c85c40ac3d33cf04a270c3b3744" reason="https://github.com/google/guava/issues/7154"/>
</artifact>
</component>
<component group="com.google.prefab" name="cli" version="2.1.0">
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
index cc8f439..f33b5d5 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
@@ -325,7 +325,7 @@
val dbFile = instrumentation.targetContext.getDatabasePath("test.db")
val driverHelper = MigrationTestHelper(
instrumentation = instrumentation,
- fileName = dbFile.path,
+ file = dbFile,
driver = AndroidSQLiteDriver(),
databaseClass = MigrationDbKotlin::class
)
diff --git a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
index 52b7c70..a8c82ee 100644
--- a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
@@ -41,7 +41,7 @@
instrumentation = instrumentation,
driver = driver,
databaseClass = AutoMigrationDatabase::class,
- fileName = file.path
+ file = file
)
override fun getTestHelper() = migrationTestHelper
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/SingleColumnRowAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/SingleColumnRowAdapter.kt
index 28a772b..a4415ba 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/SingleColumnRowAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/SingleColumnRowAdapter.kt
@@ -24,6 +24,9 @@
* Wraps a row adapter when there is only 1 item with 1 column in the response.
*/
class SingleColumnRowAdapter(val reader: CursorValueReader) : RowAdapter(reader.typeMirror()) {
+
+ override fun isMigratedToDriver(): Boolean = true
+
override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
reader.readFromCursor(outVarName, cursorVarName, "0", scope)
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
index afdf910..720724a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
@@ -1240,6 +1240,38 @@
}
@Test
+ fun queryResultAdapter_singleColumn() {
+ val src = Source.kotlin(
+ "MyDao.kt",
+ """
+ import androidx.room.*
+
+ @Dao
+ interface MyDao {
+ @Query("SELECT count(*) FROM MyEntity")
+ fun count(): Int
+
+ @Query("SELECT 'Tom' FROM MyEntity LIMIT 1")
+ fun text(): String
+
+ @Query("SELECT 'Tom' FROM MyEntity LIMIT 1")
+ fun nullableText(): String?
+ }
+
+ @Entity
+ data class MyEntity(
+ @PrimaryKey
+ val pk: Int,
+ )
+ """.trimIndent()
+ )
+ runTest(
+ sources = listOf(src, databaseSrc),
+ expectedFilePath = getTestGoldenPath(testName.methodName)
+ )
+ }
+
+ @Test
fun queryResultAdapter_list() {
val dbSource = Source.kotlin(
"MyDatabase.kt",
@@ -2048,6 +2080,9 @@
@Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
suspend fun getSuspendList(vararg arg: String?): List<MyEntity>
+ @Query("SELECT count(*) FROM MyEntity")
+ suspend fun getCount(): Int
+
@Query("INSERT INTO MyEntity (pk) VALUES (:pk)")
suspend fun insertEntity(pk: Long)
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
index a4181dc..7c8b733 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
@@ -234,23 +234,26 @@
@Override
int getAge(final int id) {
final String _sql = "SELECT ageColumn FROM user where uid = ?";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
- int _argIndex = 1;
- _statement.bindLong(_argIndex, id);
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _result;
- if (_cursor.moveToFirst()) {
- _result = _cursor.getInt(0);
- } else {
- _result = 0;
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, Integer>() {
+ @Override
+ @NonNull
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, id);
+ final int _result;
+ if (_stmt.step()) {
+ _result = (int) (_stmt.getLong(0));
+ } else {
+ _result = 0;
+ }
+ return _result;
+ } finally {
+ _stmt.close();
+ }
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
@@ -299,39 +302,41 @@
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
_stringBuilder.append(")");
final String _sql = _stringBuilder.toString();
- final int _argCount = 0 + _inputSize;
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
- int _argIndex = 1;
- if (ids == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (Integer _item : ids) {
- if (_item == null) {
- _statement.bindNull(_argIndex);
- } else {
- _statement.bindLong(_argIndex, _item);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Integer>>() {
+ @Override
+ @NonNull
+ public List<Integer> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (ids == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (Integer _item : ids) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ _stmt.bindLong(_argIndex, _item);
+ }
+ _argIndex++;
+ }
+ }
+ final List<Integer> _result = new ArrayList<Integer>();
+ while (_stmt.step()) {
+ final Integer _item_1;
+ if (_stmt.isNull(0)) {
+ _item_1 = null;
+ } else {
+ _item_1 = (int) (_stmt.getLong(0));
+ }
+ _result.add(_item_1);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- _argIndex++;
}
- }
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final List<Integer> _result = new ArrayList<Integer>();
- while (_cursor.moveToNext()) {
- final Integer _item_1;
- if (_cursor.isNull(0)) {
- _item_1 = null;
- } else {
- _item_1 = _cursor.getInt(0);
- }
- _result.add(_item_1);
- }
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
@@ -349,57 +354,59 @@
StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
_stringBuilder.append(")");
final String _sql = _stringBuilder.toString();
- final int _argCount = 0 + _inputSize + _inputSize_1 + _inputSize_2;
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
- int _argIndex = 1;
- if (ids1 == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (Integer _item : ids1) {
- if (_item == null) {
- _statement.bindNull(_argIndex);
- } else {
- _statement.bindLong(_argIndex, _item);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Integer>>() {
+ @Override
+ @NonNull
+ public List<Integer> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (ids1 == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (Integer _item : ids1) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ _stmt.bindLong(_argIndex, _item);
+ }
+ _argIndex++;
+ }
+ }
+ _argIndex = 1 + _inputSize;
+ if (ids2 == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item_1 : ids2) {
+ _stmt.bindLong(_argIndex, _item_1);
+ _argIndex++;
+ }
+ }
+ _argIndex = 1 + _inputSize + _inputSize_1;
+ if (ids3 == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item_2 : ids3) {
+ _stmt.bindLong(_argIndex, _item_2);
+ _argIndex++;
+ }
+ }
+ final List<Integer> _result = new ArrayList<Integer>();
+ while (_stmt.step()) {
+ final Integer _item_3;
+ if (_stmt.isNull(0)) {
+ _item_3 = null;
+ } else {
+ _item_3 = (int) (_stmt.getLong(0));
+ }
+ _result.add(_item_3);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- _argIndex++;
}
- }
- _argIndex = 1 + _inputSize;
- if (ids2 == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (int _item_1 : ids2) {
- _statement.bindLong(_argIndex, _item_1);
- _argIndex++;
- }
- }
- _argIndex = 1 + _inputSize + _inputSize_1;
- if (ids3 == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (int _item_2 : ids3) {
- _statement.bindLong(_argIndex, _item_2);
- _argIndex++;
- }
- }
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final List<Integer> _result = new ArrayList<Integer>();
- while (_cursor.moveToNext()) {
- final Integer _item_3;
- if (_cursor.isNull(0)) {
- _item_3 = null;
- } else {
- _item_3 = _cursor.getInt(0);
- }
- _result.add(_item_3);
- }
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
index 8b1399f..d8c8fde 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
@@ -198,23 +198,26 @@
@Override
int getAge(final int id) {
final String _sql = "SELECT ageColumn FROM user where uid = ?";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
- int _argIndex = 1;
- _statement.bindLong(_argIndex, id);
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _result;
- if (_cursor.moveToFirst()) {
- _result = _cursor.getInt(0);
- } else {
- _result = 0;
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, Integer>() {
+ @Override
+ @NonNull
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, id);
+ final int _result;
+ if (_stmt.step()) {
+ _result = (int) (_stmt.getLong(0));
+ } else {
+ _result = 0;
+ }
+ return _result;
+ } finally {
+ _stmt.close();
+ }
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
@@ -263,39 +266,41 @@
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
_stringBuilder.append(")");
final String _sql = _stringBuilder.toString();
- final int _argCount = 0 + _inputSize;
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
- int _argIndex = 1;
- if (ids == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (Integer _item : ids) {
- if (_item == null) {
- _statement.bindNull(_argIndex);
- } else {
- _statement.bindLong(_argIndex, _item);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Integer>>() {
+ @Override
+ @NonNull
+ public List<Integer> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (ids == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (Integer _item : ids) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ _stmt.bindLong(_argIndex, _item);
+ }
+ _argIndex++;
+ }
+ }
+ final List<Integer> _result = new ArrayList<Integer>();
+ while (_stmt.step()) {
+ final Integer _item_1;
+ if (_stmt.isNull(0)) {
+ _item_1 = null;
+ } else {
+ _item_1 = (int) (_stmt.getLong(0));
+ }
+ _result.add(_item_1);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- _argIndex++;
}
- }
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final List<Integer> _result = new ArrayList<Integer>();
- while (_cursor.moveToNext()) {
- final Integer _item_1;
- if (_cursor.isNull(0)) {
- _item_1 = null;
- } else {
- _item_1 = _cursor.getInt(0);
- }
- _result.add(_item_1);
- }
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
@@ -313,57 +318,59 @@
StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
_stringBuilder.append(")");
final String _sql = _stringBuilder.toString();
- final int _argCount = 0 + _inputSize + _inputSize_1 + _inputSize_2;
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
- int _argIndex = 1;
- if (ids1 == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (Integer _item : ids1) {
- if (_item == null) {
- _statement.bindNull(_argIndex);
- } else {
- _statement.bindLong(_argIndex, _item);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Integer>>() {
+ @Override
+ @NonNull
+ public List<Integer> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (ids1 == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (Integer _item : ids1) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ _stmt.bindLong(_argIndex, _item);
+ }
+ _argIndex++;
+ }
+ }
+ _argIndex = 1 + _inputSize;
+ if (ids2 == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item_1 : ids2) {
+ _stmt.bindLong(_argIndex, _item_1);
+ _argIndex++;
+ }
+ }
+ _argIndex = 1 + _inputSize + _inputSize_1;
+ if (ids3 == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item_2 : ids3) {
+ _stmt.bindLong(_argIndex, _item_2);
+ _argIndex++;
+ }
+ }
+ final List<Integer> _result = new ArrayList<Integer>();
+ while (_stmt.step()) {
+ final Integer _item_3;
+ if (_stmt.isNull(0)) {
+ _item_3 = null;
+ } else {
+ _item_3 = (int) (_stmt.getLong(0));
+ }
+ _result.add(_item_3);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- _argIndex++;
}
- }
- _argIndex = 1 + _inputSize;
- if (ids2 == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (int _item_1 : ids2) {
- _statement.bindLong(_argIndex, _item_1);
- _argIndex++;
- }
- }
- _argIndex = 1 + _inputSize + _inputSize_1;
- if (ids3 == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (int _item_2 : ids3) {
- _statement.bindLong(_argIndex, _item_2);
- _argIndex++;
- }
- }
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final List<Integer> _result = new ArrayList<Integer>();
- while (_cursor.moveToNext()) {
- final Integer _item_3;
- if (_cursor.isNull(0)) {
- _item_3 = null;
- } else {
- _item_3 = _cursor.getInt(0);
- }
- _result.add(_item_3);
- }
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
index 8987be2..c38f418 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
@@ -142,6 +142,26 @@
}
}
+ public override suspend fun getCount(): Int {
+ val _sql: String = "SELECT count(*) FROM MyEntity"
+ return performSuspending(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _result: Int
+ if (_stmt.step()) {
+ val _tmp: Int
+ _tmp = _stmt.getLong(0).toInt()
+ _result = _tmp
+ } else {
+ _result = 0
+ }
+ _result
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
public override suspend fun insertEntity(pk: Long) {
val _sql: String = "INSERT INTO MyEntity (pk) VALUES (?)"
return performSuspending(__db, false, true) { _connection ->
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_singleColumn.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_singleColumn.kt
new file mode 100644
index 0000000..d1609c4
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_singleColumn.kt
@@ -0,0 +1,86 @@
+import androidx.room.RoomDatabase
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
+import javax.`annotation`.processing.Generated
+import kotlin.Int
+import kotlin.String
+import kotlin.Suppress
+import kotlin.collections.List
+import kotlin.reflect.KClass
+
+@Generated(value = ["androidx.room.RoomProcessor"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION", "REMOVAL"])
+public class MyDao_Impl(
+ __db: RoomDatabase,
+) : MyDao {
+ private val __db: RoomDatabase
+ init {
+ this.__db = __db
+ }
+
+ public override fun count(): Int {
+ val _sql: String = "SELECT count(*) FROM MyEntity"
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _result: Int
+ if (_stmt.step()) {
+ _result = _stmt.getLong(0).toInt()
+ } else {
+ _result = 0
+ }
+ _result
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override fun text(): String {
+ val _sql: String = "SELECT 'Tom' FROM MyEntity LIMIT 1"
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _result: String
+ if (_stmt.step()) {
+ val _tmp: String
+ _tmp = _stmt.getText(0)
+ _result = _tmp
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <kotlin.String>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override fun nullableText(): String? {
+ val _sql: String = "SELECT 'Tom' FROM MyEntity LIMIT 1"
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _result: String?
+ if (_stmt.step()) {
+ val _tmp: String?
+ if (_stmt.isNull(0)) {
+ _tmp = null
+ } else {
+ _tmp = _stmt.getText(0)
+ }
+ _result = _tmp
+ } else {
+ _result = null
+ }
+ _result
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public companion object {
+ public fun getRequiredConverters(): List<KClass<*>> = emptyList()
+ }
+}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt
index 13f49dd..8782ee3 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt
@@ -17,7 +17,6 @@
package androidx.room.driver
import android.database.Cursor
-import android.database.DatabaseUtils
import androidx.annotation.RestrictTo
import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteDatabase
@@ -43,14 +42,23 @@
companion object {
fun create(db: SupportSQLiteDatabase, sql: String): SupportSQLiteStatement {
- return when (DatabaseUtils.getSqlStatementType(sql)) {
- DatabaseUtils.STATEMENT_SELECT,
- DatabaseUtils.STATEMENT_PRAGMA ->
- // Statements that return rows (SQLITE_ROW)
- SupportAndroidSQLiteStatement(db, sql)
- else ->
- // Statements that don't return row (SQLITE_DONE)
- SupportOtherAndroidSQLiteStatement(db, sql)
+ return if (isRowStatement(sql)) {
+ // Statements that return rows (SQLITE_ROW)
+ SupportAndroidSQLiteStatement(db, sql)
+ } else {
+ // Statements that don't return row (SQLITE_DONE)
+ SupportOtherAndroidSQLiteStatement(db, sql)
+ }
+ }
+
+ private fun isRowStatement(sql: String): Boolean {
+ val prefix = sql.trim()
+ if (prefix.length < 3) {
+ return false
+ }
+ return when (prefix.substring(0, 3).uppercase()) {
+ "SEL", "PRA", "WIT" -> true
+ else -> false
}
}
}
diff --git a/room/room-testing/api/current.txt b/room/room-testing/api/current.txt
index 2499cb4..92b8b5c 100644
--- a/room/room-testing/api/current.txt
+++ b/room/room-testing/api/current.txt
@@ -2,12 +2,12 @@
package androidx.room.testing {
public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+ ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, java.io.File file, androidx.sqlite.SQLiteDriver driver, kotlin.reflect.KClass<? extends androidx.room.RoomDatabase> databaseClass, optional kotlin.jvm.functions.Function0<? extends androidx.room.RoomDatabase> databaseFactory, optional java.util.List<? extends androidx.room.migration.AutoMigrationSpec> autoMigrationSpecs);
ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass);
ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs);
ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder);
ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
- ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, String fileName, androidx.sqlite.SQLiteDriver driver, kotlin.reflect.KClass<? extends androidx.room.RoomDatabase> databaseClass, optional kotlin.jvm.functions.Function0<? extends androidx.room.RoomDatabase> databaseFactory, optional java.util.List<? extends androidx.room.migration.AutoMigrationSpec> autoMigrationSpecs);
method public void closeWhenFinished(androidx.room.RoomDatabase db);
method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase db);
method public final androidx.sqlite.SQLiteConnection createDatabase(int version);
diff --git a/room/room-testing/api/restricted_current.txt b/room/room-testing/api/restricted_current.txt
index 2499cb4..92b8b5c 100644
--- a/room/room-testing/api/restricted_current.txt
+++ b/room/room-testing/api/restricted_current.txt
@@ -2,12 +2,12 @@
package androidx.room.testing {
public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+ ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, java.io.File file, androidx.sqlite.SQLiteDriver driver, kotlin.reflect.KClass<? extends androidx.room.RoomDatabase> databaseClass, optional kotlin.jvm.functions.Function0<? extends androidx.room.RoomDatabase> databaseFactory, optional java.util.List<? extends androidx.room.migration.AutoMigrationSpec> autoMigrationSpecs);
ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass);
ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs);
ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder);
ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
- ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, String fileName, androidx.sqlite.SQLiteDriver driver, kotlin.reflect.KClass<? extends androidx.room.RoomDatabase> databaseClass, optional kotlin.jvm.functions.Function0<? extends androidx.room.RoomDatabase> databaseFactory, optional java.util.List<? extends androidx.room.migration.AutoMigrationSpec> autoMigrationSpecs);
method public void closeWhenFinished(androidx.room.RoomDatabase db);
method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase db);
method public final androidx.sqlite.SQLiteConnection createDatabase(int version);
diff --git a/room/room-testing/src/androidMain/kotlin/androidx/room/testing/MigrationTestHelper.android.kt b/room/room-testing/src/androidMain/kotlin/androidx/room/testing/MigrationTestHelper.android.kt
index cec9729..9e4a324 100644
--- a/room/room-testing/src/androidMain/kotlin/androidx/room/testing/MigrationTestHelper.android.kt
+++ b/room/room-testing/src/androidMain/kotlin/androidx/room/testing/MigrationTestHelper.android.kt
@@ -34,6 +34,7 @@
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
+import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.lang.ref.WeakReference
@@ -179,7 +180,7 @@
* be used.
*
* @param instrumentation The instrumentation instance.
- * @param fileName Name of the database.
+ * @param file The database file.
* @param driver A driver that opens connection to a file database. A driver that opens connections
* to an in-memory database would be meaningless.
* @param databaseClass The [androidx.room.Database] annotated class.
@@ -189,9 +190,10 @@
* @param autoMigrationSpecs The list of [androidx.room.ProvidedAutoMigrationSpec] instances
* for [androidx.room.AutoMigration]s that require them.
*/
+ @Suppress("StreamFiles")
constructor(
instrumentation: Instrumentation,
- fileName: String,
+ file: File,
driver: SQLiteDriver,
databaseClass: KClass<out RoomDatabase>,
databaseFactory: () -> RoomDatabase = {
@@ -209,7 +211,7 @@
this.delegate = SQLiteDriverMigrationTestHelper(
instrumentation = instrumentation,
assetsFolder = assetsFolder,
- fileName = fileName,
+ file = file,
driver = driver,
databaseClass = databaseClass,
databaseFactory = databaseFactory,
@@ -578,7 +580,7 @@
private val driver: SQLiteDriver,
databaseClass: KClass<out RoomDatabase>,
databaseFactory: () -> RoomDatabase,
- private val fileName: String,
+ private val file: File,
private val autoMigrationSpecs: List<AutoMigrationSpec>
) : AndroidMigrationTestHelper(instrumentation, assetsFolder) {
@@ -612,5 +614,5 @@
}
private fun createConfiguration(container: RoomDatabase.MigrationContainer) =
- createDatabaseConfiguration(container, null, driver, fileName)
+ createDatabaseConfiguration(container, null, driver, file.path)
}
diff --git a/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt b/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt
index fe51e5b..4c8d2f2 100644
--- a/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt
+++ b/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt
@@ -288,10 +288,10 @@
}
@Test
- fun clearBindings() = testWithConnection {
- it.execSQL("CREATE TABLE Foo (id)")
- it.execSQL("INSERT INTO Foo (id) VALUES (1)")
- it.prepare("SELECT * FROM Foo WHERE id = ?").use {
+ fun clearBindings() = testWithConnection { connection ->
+ connection.execSQL("CREATE TABLE Foo (id)")
+ connection.execSQL("INSERT INTO Foo (id) VALUES (1)")
+ connection.prepare("SELECT * FROM Foo WHERE id = ?").use {
it.bindLong(1, 1)
assertThat(it.step()).isTrue()
it.reset()
@@ -334,6 +334,23 @@
assertThat(changes).isEqualTo(3)
}
+ @Test
+ fun withClause() = testWithConnection { connection ->
+ var seriesSum = 0
+ connection.prepare(
+ """
+ WITH RECURSIVE
+ cnt(x) AS (VALUES(1) UNION ALL SELECT x + 1 FROM cnt WHERE x < 10)
+ SELECT x FROM cnt;
+ """.trimIndent()
+ ).use {
+ while (it.step()) {
+ seriesSum += it.getInt(0)
+ }
+ }
+ assertThat(seriesSum).isEqualTo(55)
+ }
+
private inline fun testWithConnection(block: (SQLiteConnection) -> Unit) {
val driver = getDriver()
val connection = driver.open(":memory:")
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteStatement.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteStatement.android.kt
index 1ee0693..f8ac8bc 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteStatement.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteStatement.android.kt
@@ -17,7 +17,6 @@
package androidx.sqlite.driver
import android.database.Cursor
-import android.database.DatabaseUtils
import android.database.sqlite.SQLiteCursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteProgram
@@ -43,14 +42,23 @@
companion object {
fun create(db: SQLiteDatabase, sql: String): AndroidSQLiteStatement {
- return when (DatabaseUtils.getSqlStatementType(sql)) {
- DatabaseUtils.STATEMENT_SELECT,
- DatabaseUtils.STATEMENT_PRAGMA ->
- // Statements that return rows (SQLITE_ROW)
- SelectAndroidSQLiteStatement(db, sql)
- else ->
- // Statements that don't return row (SQLITE_DONE)
- OtherAndroidSQLiteStatement(db, sql)
+ return if (isRowStatement(sql)) {
+ // Statements that return rows (SQLITE_ROW)
+ SelectAndroidSQLiteStatement(db, sql)
+ } else {
+ // Statements that don't return row (SQLITE_DONE)
+ OtherAndroidSQLiteStatement(db, sql)
+ }
+ }
+
+ private fun isRowStatement(sql: String): Boolean {
+ val prefix = sql.trim()
+ if (prefix.length < 3) {
+ return false
+ }
+ return when (prefix.substring(0, 3).uppercase()) {
+ "SEL", "PRA", "WIT" -> true
+ else -> false
}
}
}
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 9583714..4e0cd71c 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -1198,6 +1198,40 @@
val userStyleFlavors: UserStyleFlavors
)
+ internal class ChoreographerCallback(
+ val watchFaceImpl: WatchFaceImpl
+ ) : Choreographer.FrameCallback {
+ /**
+ * Whether we already have a frameCallback posted and waiting in the [Choreographer]
+ * queue. This protects us from drawing multiple times in a single frame.
+ */
+ var frameCallbackPending = false
+
+ override fun doFrame(frameTimeNs: Long) {
+ frameCallbackPending = false
+
+ /**
+ * It's possible we went ambient by the time our callback occurred in which case
+ * there's no point drawing.
+ */
+ if (watchFaceImpl.renderer.shouldAnimate()) {
+ try {
+ if (TRACE_DRAW) {
+ Trace.beginSection("onDraw")
+ }
+ if (LOG_VERBOSE) {
+ Log.v(TAG, "drawing frame")
+ }
+ watchFaceImpl.onDraw()
+ } finally {
+ if (TRACE_DRAW) {
+ Trace.endSection()
+ }
+ }
+ }
+ }
+ }
+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@OptIn(WatchFaceExperimental::class)
public inner class EngineWrapper(
@@ -1272,41 +1306,13 @@
override val systemTimeProvider = getSystemTimeProvider()
override val wearSdkVersion = this@WatchFaceService.wearPlatformVersion
- /**
- * Whether we already have a [frameCallback] posted and waiting in the [Choreographer]
- * queue. This protects us from drawing multiple times in a single frame.
- */
- private var frameCallbackPending = false
-
internal var editorObscuresWatchFace = false
set(value) {
getWatchFaceImplOrNull()?.editorObscuresWatchFace = value
field = value
}
- private val frameCallback =
- object : Choreographer.FrameCallback {
- override fun doFrame(frameTimeNs: Long) {
- if (destroyed) {
- return
- }
- require(allowWatchfaceToAnimate) {
- "Choreographer doFrame called but allowWatchfaceToAnimate is false"
- }
- frameCallbackPending = false
-
- val watchFaceImpl: WatchFaceImpl? = getWatchFaceImplOrNull()
-
- /**
- * It's possible we went ambient by the time our callback occurred in which case
- * there's no point drawing.
- */
- if (watchFaceImpl?.renderer?.shouldAnimate() != false) {
- draw(watchFaceImpl)
- }
- }
- }
-
+ private var frameCallback: ChoreographerCallback? = null
private val invalidateRunnable = Runnable(this::invalidate)
// If non-null then changes to the style must be persisted.
@@ -1863,7 +1869,7 @@
quitBackgroundThreadIfCreated()
uiThreadHandler.removeCallbacks(invalidateRunnable)
if (this::choreographer.isInitialized) {
- choreographer.removeFrameCallback(frameCallback)
+ frameCallback?.let { choreographer.removeFrameCallback(it) }
}
if (this::interactiveInstanceId.isInitialized) {
InteractiveInstanceManager.deleteInstance(interactiveInstanceId)
@@ -2446,6 +2452,9 @@
// executed. NB usually we won't have to wait at all.
initStyleAndComplicationsDone.await()
deferredWatchFaceImpl.complete(watchFaceImpl)
+ frameCallback = ChoreographerCallback(watchFaceImpl)
+ // Start issuing choreographer frames.
+ invalidate()
asyncWatchFaceConstructionPending = false
watchFaceImpl.initComplete = true
@@ -2592,18 +2601,20 @@
if (!allowWatchfaceToAnimate) {
return
}
- if (!frameCallbackPending) {
- if (LOG_VERBOSE) {
- Log.v(TAG, "invalidate: requesting draw")
- }
- frameCallbackPending = true
- if (!this::choreographer.isInitialized) {
- choreographer = getChoreographer()
- }
- choreographer.postFrameCallback(frameCallback)
- } else {
- if (LOG_VERBOSE) {
- Log.v(TAG, "invalidate: draw already requested")
+ frameCallback?.let {
+ if (!it.frameCallbackPending) {
+ if (LOG_VERBOSE) {
+ Log.v(TAG, "invalidate: requesting draw")
+ }
+ it.frameCallbackPending = true
+ if (!this::choreographer.isInitialized) {
+ choreographer = getChoreographer()
+ }
+ choreographer.postFrameCallback(it)
+ } else {
+ if (LOG_VERBOSE) {
+ Log.v(TAG, "invalidate: draw already requested")
+ }
}
}
}
@@ -2954,7 +2965,7 @@
writer.println("interactiveInstanceId=$interactiveInstanceId")
}
- writer.println("frameCallbackPending=$frameCallbackPending")
+ writer.println("frameCallbackPending=${frameCallback?.frameCallbackPending}")
writer.println("destroyed=$destroyed")
writer.println("surfaceDestroyed=$surfaceDestroyed")
writer.println("lastComplications=${complicationsFlow.value}")
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index 10931bf..b691e7ac 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -100,6 +100,8 @@
import com.google.common.util.concurrent.ListenableFuture;
+import kotlinx.coroutines.Dispatchers;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -118,7 +120,6 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-import kotlinx.coroutines.Dispatchers;
@RunWith(AndroidJUnit4.class)
public class WorkerWrapperTest extends DatabaseTest {
@@ -181,7 +182,7 @@
FutureListener listener = createAndAddFutureListener(workerWrapper);
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(SUCCEEDED));
- assertBeginEndTraceSpans(work);
+ assertBeginEndTraceSpans(work.getWorkSpec());
}
@Test
@@ -194,7 +195,7 @@
.launch();
WorkSpec latestWorkSpec = mWorkSpecDao.getWorkSpec(work.getStringId());
assertThat(latestWorkSpec.runAttemptCount, is(1));
- assertBeginEndTraceSpans(work);
+ assertBeginEndTraceSpans(work.getWorkSpec());
}
@Test
@@ -207,7 +208,7 @@
.launch();
WorkSpec latestWorkSpec = mWorkSpecDao.getWorkSpec(work.getStringId());
assertThat(latestWorkSpec.runAttemptCount, is(1));
- assertBeginEndTraceSpans(work);
+ assertBeginEndTraceSpans(work.getWorkSpec());
}
@Test
@@ -250,7 +251,7 @@
.withWorker(usedWorker)
.build();
workerWrapper.launch();
- assertBeginEndTraceSpans(work);
+ assertBeginEndTraceSpans(work.getWorkSpec());
}
@Test
@@ -276,7 +277,7 @@
FutureListener listener = createAndAddFutureListener(workerWrapper);
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(CANCELLED));
- assertBeginEndTraceSpans(work);
+ assertBeginEndTraceSpans(work.getWorkSpec());
}
@Test
@@ -344,7 +345,7 @@
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
assertThat(listener.mResult, is(true));
- assertBeginEndTraceSpans(work);
+ assertBeginEndTraceSpans(work.getWorkSpec());
}
@Test
@@ -373,7 +374,7 @@
List<Data> arguments = mWorkSpecDao.getInputsFromPrerequisites(work.getStringId());
assertThat(arguments.size(), is(1));
assertThat(arguments, contains(ChainedArgumentWorker.getChainedArguments()));
- assertBeginEndTraceSpans(prerequisiteWork);
+ assertBeginEndTraceSpans(prerequisiteWork.getWorkSpec());
}
@Test
@@ -502,7 +503,7 @@
assertThat(mWorkSpecDao.getState(work.getStringId()),
isOneOf(ENQUEUED, RUNNING, SUCCEEDED));
assertThat(mWorkSpecDao.getState(cancelledWork.getStringId()), is(CANCELLED));
- assertBeginEndTraceSpans(prerequisiteWork);
+ assertBeginEndTraceSpans(prerequisiteWork.getWorkSpec());
}
@Test
@@ -565,7 +566,7 @@
WorkSpec workSpec = mWorkSpecDao.getWorkSpec(retryWork.getStringId());
// The run attempt count should remain the same
assertThat(workSpec.runAttemptCount, is(1));
- assertBeginEndTraceSpans(retryWork);
+ assertBeginEndTraceSpans(workSpec);
}
@Test
@@ -586,7 +587,7 @@
WorkSpec updatedWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId());
assertThat(updatedWorkSpec.calculateNextRunTime(), greaterThan(periodStartTimeMillis));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@Test
@@ -607,7 +608,7 @@
WorkSpec updatedWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId());
assertThat(updatedWorkSpec.calculateNextRunTime(), greaterThan(periodStartTimeMillis));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@Test
@@ -628,7 +629,7 @@
assertThat(listener.mResult, is(false));
assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(0));
assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@Test
@@ -649,7 +650,7 @@
assertThat(listener.mResult, is(false));
assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(0));
assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@Test
@@ -670,7 +671,7 @@
assertThat(listener.mResult, is(true));
assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(1));
assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@@ -693,7 +694,7 @@
FutureListener listener = createAndAddFutureListener(workerWrapper);
// Should get rescheduled
assertThat(listener.mResult, is(true));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@Test
@@ -715,7 +716,7 @@
FutureListener listener = createAndAddFutureListener(workerWrapper);
// Should get rescheduled because flex should be respected.
assertThat(listener.mResult, is(true));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@Test
@@ -842,7 +843,7 @@
assertThat(afterRunWorkSpec.getNextScheduleTimeOverride(), equalTo(secondOverride));
assertThat(afterRunWorkSpec.calculateNextRunTime(),
equalTo(secondOverride));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@Test
@@ -897,7 +898,7 @@
assertThat(afterRunWorkSpec.getNextScheduleTimeOverride(), equalTo(secondOverrideMillis));
assertThat(afterRunWorkSpec.calculateNextRunTime(),
equalTo(secondOverrideMillis));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@Test
@@ -945,7 +946,7 @@
// Normal next period is scheduled.
assertThat(afterRunWorkSpec.calculateNextRunTime(),
equalTo(mTestClock.currentTimeMillis + intervalDurationMillis));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@@ -997,7 +998,7 @@
// Backoff timing is respected
assertThat(afterRunWorkSpec.calculateNextRunTime(),
equalTo(mTestClock.currentTimeMillis + backoffLinearDurationMillis));
- assertBeginEndTraceSpans(periodicWork);
+ assertBeginEndTraceSpans(periodicWork.getWorkSpec());
}
@NonNull
@@ -1423,15 +1424,15 @@
return listener;
}
- private void assertBeginEndTraceSpans(WorkRequest workRequest) {
+ private void assertBeginEndTraceSpans(WorkSpec workSpec) {
ArgumentCaptor<String> traceSpan = ArgumentCaptor.forClass(String.class);
- ArgumentCaptor<Integer> generation = ArgumentCaptor.forClass(Integer.class);
- verify(mTracer).beginAsyncSection(traceSpan.capture(), generation.capture());
- assertThat(workRequest.getWorkSpec().workerClassName, containsString(traceSpan.getValue()));
- assertThat(workRequest.getWorkSpec().getGeneration(), is(generation.getValue()));
- verify(mTracer).beginAsyncSection(traceSpan.capture(), generation.capture());
- assertThat(workRequest.getWorkSpec().workerClassName, containsString(traceSpan.getValue()));
- assertThat(workRequest.getWorkSpec().getGeneration(), is(generation.getValue()));
+ ArgumentCaptor<Integer> cookie = ArgumentCaptor.forClass(Integer.class);
+ verify(mTracer).beginAsyncSection(traceSpan.capture(), cookie.capture());
+ assertThat(workSpec.workerClassName, containsString(traceSpan.getValue()));
+ assertThat(workSpec.hashCode(), is(cookie.getValue()));
+ verify(mTracer).beginAsyncSection(traceSpan.capture(), cookie.capture());
+ assertThat(workSpec.workerClassName, containsString(traceSpan.getValue()));
+ assertThat(workSpec.hashCode(), is(cookie.getValue()));
}
private static class FutureListener implements Runnable {
@@ -1465,6 +1466,4 @@
this.mThrowable = params.getThrowable();
}
}
-
- ;
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
index 22530dc..ba42d86 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
@@ -128,7 +128,10 @@
if (traceTag != null) {
configuration.tracer.beginAsyncSection(
traceTag,
- workGenerationalId.generation
+ // Use hashCode() instead of a generational id given we want to allow concurrent
+ // execution of Workers with the same name. Additionally `generation` is already
+ // a part of the WorkSpec's hashCode.
+ workSpec.hashCode()
)
}
// Needed for nested transactions, such as when we're in a dependent work request when
@@ -256,7 +259,7 @@
if (traceTag != null) {
configuration.tracer.endAsyncSection(
traceTag,
- workGenerationalId.generation
+ workSpec.hashCode()
)
}
}