summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2021-08-13 09:48:56 +0000
committerMoonchild <moonchild@palemoon.org>2021-08-13 09:48:56 +0000
commit02882c368084ffff532d185571910bbe4f052531 (patch)
tree70e073a9f1500583e02a8b77c8583081e6915407
parentfeafc2f129df921f9e2954f0a21b1060588e4a0f (diff)
parent9baa17379ea6281bb47c66b7932d80e8d3362406 (diff)
downloaduxp-02882c368084ffff532d185571910bbe4f052531.tar.gz
Merge branch 'master' into releaseRC_20210813
-rw-r--r--config/milestone.txt2
-rw-r--r--db/sqlite3/src/sqlite3.c17108
-rw-r--r--db/sqlite3/src/sqlite3.h257
-rw-r--r--dom/html/HTMLObjectElement.cpp8
-rw-r--r--dom/html/HTMLSharedObjectElement.cpp9
-rw-r--r--dom/media/CubebUtils.cpp31
-rw-r--r--dom/media/CubebUtils.h2
-rw-r--r--dom/media/GraphDriver.cpp10
-rw-r--r--dom/workers/WorkerPrivate.cpp7
-rw-r--r--js/src/builtin/Promise.cpp319
-rw-r--r--js/src/jit/MacroAssembler.h9
-rw-r--r--js/src/moz.build2
-rw-r--r--js/src/new-regexp/RegExpTypes.h (renamed from js/src/regexp/RegExpTypes.h)0
-rw-r--r--js/src/new-regexp/VERSION (renamed from js/src/regexp/VERSION)0
-rw-r--r--js/src/new-regexp/gen-regexp-special-case.cc (renamed from js/src/regexp/gen-regexp-special-case.cc)2
-rw-r--r--js/src/new-regexp/import-irregexp.py (renamed from js/src/regexp/import-irregexp.py)0
-rw-r--r--js/src/new-regexp/moz.build (renamed from js/src/regexp/moz.build)7
-rw-r--r--js/src/new-regexp/property-sequences.cc (renamed from js/src/regexp/property-sequences.cc)2
-rw-r--r--js/src/new-regexp/property-sequences.h (renamed from js/src/regexp/property-sequences.h)2
-rw-r--r--js/src/new-regexp/regexp-ast.cc (renamed from js/src/regexp/regexp-ast.cc)2
-rw-r--r--js/src/new-regexp/regexp-ast.h (renamed from js/src/regexp/regexp-ast.h)2
-rw-r--r--js/src/new-regexp/regexp-bytecode-generator-inl.h (renamed from js/src/regexp/regexp-bytecode-generator-inl.h)4
-rw-r--r--js/src/new-regexp/regexp-bytecode-generator.cc (renamed from js/src/regexp/regexp-bytecode-generator.cc)10
-rw-r--r--js/src/new-regexp/regexp-bytecode-generator.h (renamed from js/src/regexp/regexp-bytecode-generator.h)2
-rw-r--r--js/src/new-regexp/regexp-bytecode-peephole.cc (renamed from js/src/regexp/regexp-bytecode-peephole.cc)4
-rw-r--r--js/src/new-regexp/regexp-bytecode-peephole.h (renamed from js/src/regexp/regexp-bytecode-peephole.h)2
-rw-r--r--js/src/new-regexp/regexp-bytecodes.cc (renamed from js/src/regexp/regexp-bytecodes.cc)2
-rw-r--r--js/src/new-regexp/regexp-bytecodes.h (renamed from js/src/regexp/regexp-bytecodes.h)2
-rw-r--r--js/src/new-regexp/regexp-compiler-tonode.cc (renamed from js/src/regexp/regexp-compiler-tonode.cc)6
-rw-r--r--js/src/new-regexp/regexp-compiler.cc (renamed from js/src/regexp/regexp-compiler.cc)6
-rw-r--r--js/src/new-regexp/regexp-compiler.h (renamed from js/src/regexp/regexp-compiler.h)2
-rw-r--r--js/src/new-regexp/regexp-dotprinter.cc (renamed from js/src/regexp/regexp-dotprinter.cc)4
-rw-r--r--js/src/new-regexp/regexp-dotprinter.h (renamed from js/src/regexp/regexp-dotprinter.h)2
-rw-r--r--js/src/new-regexp/regexp-error.cc (renamed from js/src/regexp/regexp-error.cc)2
-rw-r--r--js/src/new-regexp/regexp-error.h (renamed from js/src/regexp/regexp-error.h)0
-rw-r--r--js/src/new-regexp/regexp-interpreter.cc (renamed from js/src/regexp/regexp-interpreter.cc)11
-rw-r--r--js/src/new-regexp/regexp-interpreter.h (renamed from js/src/regexp/regexp-interpreter.h)2
-rw-r--r--js/src/new-regexp/regexp-macro-assembler-arch.h (renamed from js/src/regexp/regexp-macro-assembler-arch.h)4
-rw-r--r--js/src/new-regexp/regexp-macro-assembler-tracer.cc (renamed from js/src/regexp/regexp-macro-assembler-tracer.cc)2
-rw-r--r--js/src/new-regexp/regexp-macro-assembler-tracer.h (renamed from js/src/regexp/regexp-macro-assembler-tracer.h)2
-rw-r--r--js/src/new-regexp/regexp-macro-assembler.cc (renamed from js/src/regexp/regexp-macro-assembler.cc)4
-rw-r--r--js/src/new-regexp/regexp-macro-assembler.h (renamed from js/src/regexp/regexp-macro-assembler.h)6
-rw-r--r--js/src/new-regexp/regexp-native-macro-assembler.cc (renamed from js/src/regexp/regexp-native-macro-assembler.cc)42
-rw-r--r--js/src/new-regexp/regexp-nodes.h (renamed from js/src/regexp/regexp-nodes.h)2
-rw-r--r--js/src/new-regexp/regexp-parser.cc (renamed from js/src/regexp/regexp-parser.cc)8
-rw-r--r--js/src/new-regexp/regexp-parser.h (renamed from js/src/regexp/regexp-parser.h)8
-rw-r--r--js/src/new-regexp/regexp-shim.cc (renamed from js/src/regexp/regexp-shim.cc)8
-rw-r--r--js/src/new-regexp/regexp-shim.h (renamed from js/src/regexp/regexp-shim.h)10
-rw-r--r--js/src/new-regexp/regexp-stack.cc (renamed from js/src/regexp/regexp-stack.cc)2
-rw-r--r--js/src/new-regexp/regexp-stack.h (renamed from js/src/regexp/regexp-stack.h)2
-rw-r--r--js/src/new-regexp/regexp.h (renamed from js/src/regexp/regexp.h)4
-rw-r--r--js/src/new-regexp/special-case.cc (renamed from js/src/regexp/special-case.cc)2
-rw-r--r--js/src/new-regexp/special-case.h (renamed from js/src/regexp/special-case.h)2
-rw-r--r--js/src/new-regexp/util/flags.h (renamed from js/src/regexp/util/flags.h)0
-rw-r--r--js/src/new-regexp/util/unicode.cc (renamed from js/src/regexp/util/unicode.cc)2
-rw-r--r--js/src/new-regexp/util/vector.h (renamed from js/src/regexp/util/vector.h)4
-rw-r--r--js/src/new-regexp/util/zone.h (renamed from js/src/regexp/util/zone.h)2
-rw-r--r--js/src/vm/CommonPropertyNames.h1
-rw-r--r--layout/media/symbols.def.in1
-rw-r--r--layout/style/nsRuleNode.cpp4
-rw-r--r--media/libcubeb/AUTHORS1
-rw-r--r--media/libcubeb/README.md5
-rw-r--r--media/libcubeb/README_MCP8
-rw-r--r--media/libcubeb/README_MOZILLA8
-rw-r--r--media/libcubeb/bug1292803_pulse_assert.patch46
-rw-r--r--media/libcubeb/bug1302231_emergency_bailout.patch140
-rw-r--r--media/libcubeb/fix-crashes.patch71
-rw-r--r--media/libcubeb/gtest/common.h145
-rw-r--r--media/libcubeb/gtest/test_audio.cpp244
-rw-r--r--media/libcubeb/gtest/test_devices.cpp255
-rw-r--r--media/libcubeb/gtest/test_duplex.cpp315
-rw-r--r--media/libcubeb/gtest/test_latency.cpp47
-rw-r--r--media/libcubeb/gtest/test_loopback.cpp578
-rw-r--r--media/libcubeb/gtest/test_overload_callback.cpp92
-rw-r--r--media/libcubeb/gtest/test_record.cpp116
-rw-r--r--media/libcubeb/gtest/test_resampler.cpp1081
-rw-r--r--media/libcubeb/gtest/test_ring_array.cpp73
-rw-r--r--media/libcubeb/gtest/test_sanity.cpp (renamed from media/libcubeb/tests/test_sanity.cpp)548
-rw-r--r--media/libcubeb/gtest/test_tone.cpp (renamed from media/libcubeb/tests/test_tone.cpp)96
-rw-r--r--media/libcubeb/gtest/test_utils.cpp72
-rw-r--r--media/libcubeb/include/cubeb.h518
-rw-r--r--media/libcubeb/osx-linearize-operations.patch968
-rw-r--r--media/libcubeb/prevent-double-free.patch46
-rw-r--r--media/libcubeb/src/android/audiotrack_definitions.h81
-rw-r--r--media/libcubeb/src/android/cubeb-output-latency.h76
-rw-r--r--media/libcubeb/src/android/cubeb_media_library.h64
-rw-r--r--media/libcubeb/src/android/sles_definitions.h69
-rw-r--r--media/libcubeb/src/audiotrack_definitions.h72
-rw-r--r--media/libcubeb/src/cubeb-internal.h86
-rw-r--r--media/libcubeb/src/cubeb-jni.cpp80
-rw-r--r--media/libcubeb/src/cubeb-jni.h13
-rw-r--r--media/libcubeb/src/cubeb-sles.h26
-rw-r--r--media/libcubeb/src/cubeb.c477
-rw-r--r--media/libcubeb/src/cubeb_aaudio.cpp1504
-rw-r--r--media/libcubeb/src/cubeb_alsa.c694
-rw-r--r--media/libcubeb/src/cubeb_android.h17
-rw-r--r--media/libcubeb/src/cubeb_array_queue.h99
-rw-r--r--media/libcubeb/src/cubeb_assert.h27
-rw-r--r--media/libcubeb/src/cubeb_audiotrack.c438
-rw-r--r--media/libcubeb/src/cubeb_audiounit.cpp3642
-rw-r--r--media/libcubeb/src/cubeb_jack.cpp693
-rw-r--r--media/libcubeb/src/cubeb_log.cpp132
-rw-r--r--media/libcubeb/src/cubeb_log.h39
-rw-r--r--media/libcubeb/src/cubeb_mixer.cpp625
-rw-r--r--media/libcubeb/src/cubeb_mixer.h36
-rw-r--r--media/libcubeb/src/cubeb_opensl.c1775
-rw-r--r--media/libcubeb/src/cubeb_oss.c1329
-rw-r--r--media/libcubeb/src/cubeb_osx_run_loop.h23
-rw-r--r--media/libcubeb/src/cubeb_pulse.c1385
-rw-r--r--media/libcubeb/src/cubeb_resampler.cpp349
-rw-r--r--media/libcubeb/src/cubeb_resampler.h43
-rw-r--r--media/libcubeb/src/cubeb_resampler_internal.h348
-rw-r--r--media/libcubeb/src/cubeb_ring_array.h40
-rw-r--r--media/libcubeb/src/cubeb_ringbuffer.h468
-rw-r--r--media/libcubeb/src/cubeb_sndio.c556
-rw-r--r--media/libcubeb/src/cubeb_strings.c154
-rw-r--r--media/libcubeb/src/cubeb_strings.h47
-rw-r--r--media/libcubeb/src/cubeb_sun.c991
-rw-r--r--media/libcubeb/src/cubeb_utils.cpp25
-rw-r--r--media/libcubeb/src/cubeb_utils.h226
-rw-r--r--media/libcubeb/src/cubeb_utils_unix.h21
-rw-r--r--media/libcubeb/src/cubeb_utils_win.h23
-rw-r--r--media/libcubeb/src/cubeb_wasapi.cpp3112
-rw-r--r--media/libcubeb/src/cubeb_winmm.c1067
-rw-r--r--media/libcubeb/src/moz.build47
-rw-r--r--media/libcubeb/tests/common.h61
-rw-r--r--media/libcubeb/tests/test_audio.cpp294
-rw-r--r--media/libcubeb/tests/test_devices.cpp162
-rw-r--r--media/libcubeb/tests/test_duplex.cpp151
-rw-r--r--media/libcubeb/tests/test_latency.cpp60
-rw-r--r--media/libcubeb/tests/test_record.cpp125
-rw-r--r--media/libcubeb/tests/test_resampler.cpp554
-rw-r--r--media/libcubeb/tests/test_utils.cpp80
-rw-r--r--media/libcubeb/unresampled-frames.patch36
-rwxr-xr-xmedia/libcubeb/update.sh80
-rw-r--r--media/libcubeb/uplift-part-of-f07ee6d-esr52.patch167
-rw-r--r--media/libcubeb/uplift-patch-7a4c711.patch69
-rw-r--r--media/libcubeb/uplift-system-listener-patch.patch402
-rw-r--r--media/libcubeb/uplift-wasapi-part-to-beta.patch118
-rw-r--r--mfbt/LinkedList.h6
-rw-r--r--mfbt/SegmentedVector.h36
-rw-r--r--netwerk/cache2/CacheIndex.cpp399
-rw-r--r--netwerk/cache2/CacheIndex.h237
-rw-r--r--netwerk/cache2/CacheIndexContextIterator.cpp15
-rw-r--r--netwerk/cache2/CacheIndexContextIterator.h4
-rw-r--r--netwerk/cache2/CacheIndexIterator.cpp27
-rw-r--r--netwerk/cache2/CacheIndexIterator.h17
-rw-r--r--old-configure.in2
-rw-r--r--xpcom/base/nsCycleCollector.cpp290
149 files changed, 29435 insertions, 18413 deletions
diff --git a/config/milestone.txt b/config/milestone.txt
index 6fa2a2e979..2465c79074 100644
--- a/config/milestone.txt
+++ b/config/milestone.txt
@@ -10,4 +10,4 @@
# hardcoded milestones in the tree from these two files.
#--------------------------------------------------------
-4.8.2 \ No newline at end of file
+4.8.3 \ No newline at end of file
diff --git a/db/sqlite3/src/sqlite3.c b/db/sqlite3/src/sqlite3.c
index a82744931c..89faea5b23 100644
--- a/db/sqlite3/src/sqlite3.c
+++ b/db/sqlite3/src/sqlite3.c
@@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
-** version 3.33.0. By combining all the individual C code files into this
+** version 3.36.0. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@@ -83,8 +83,10 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_64BIT_STATS
"64BIT_STATS",
#endif
-#if SQLITE_ALLOW_COVERING_INDEX_SCAN
- "ALLOW_COVERING_INDEX_SCAN",
+#ifdef SQLITE_ALLOW_COVERING_INDEX_SCAN
+# if SQLITE_ALLOW_COVERING_INDEX_SCAN != 1
+ "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN),
+# endif
#endif
#if SQLITE_ALLOW_URI_AUTHORITY
"ALLOW_URI_AUTHORITY",
@@ -146,8 +148,10 @@ static const char * const sqlite3azCompileOpt[] = {
#ifdef SQLITE_DEFAULT_LOOKASIDE
"DEFAULT_LOOKASIDE=" CTIMEOPT_VAL2(SQLITE_DEFAULT_LOOKASIDE),
#endif
-#if SQLITE_DEFAULT_MEMSTATUS
- "DEFAULT_MEMSTATUS",
+#ifdef SQLITE_DEFAULT_MEMSTATUS
+# if SQLITE_DEFAULT_MEMSTATUS != 1
+ "DEFAULT_MEMSTATUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_MEMSTATUS),
+# endif
#endif
#ifdef SQLITE_DEFAULT_MMAP_SIZE
"DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE),
@@ -221,7 +225,7 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_ENABLE_BYTECODE_VTAB
"ENABLE_BYTECODE_VTAB",
#endif
-#if SQLITE_ENABLE_CEROD
+#ifdef SQLITE_ENABLE_CEROD
"ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD),
#endif
#if SQLITE_ENABLE_COLUMN_METADATA
@@ -236,17 +240,17 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_ENABLE_CURSOR_HINTS
"ENABLE_CURSOR_HINTS",
#endif
+#if SQLITE_ENABLE_DBPAGE_VTAB
+ "ENABLE_DBPAGE_VTAB",
+#endif
#if SQLITE_ENABLE_DBSTAT_VTAB
"ENABLE_DBSTAT_VTAB",
#endif
#if SQLITE_ENABLE_EXPENSIVE_ASSERT
"ENABLE_EXPENSIVE_ASSERT",
#endif
-#if SQLITE_ENABLE_FTS1
- "ENABLE_FTS1",
-#endif
-#if SQLITE_ENABLE_FTS2
- "ENABLE_FTS2",
+#if SQLITE_ENABLE_EXPLAIN_COMMENTS
+ "ENABLE_EXPLAIN_COMMENTS",
#endif
#if SQLITE_ENABLE_FTS3
"ENABLE_FTS3",
@@ -284,6 +288,9 @@ static const char * const sqlite3azCompileOpt[] = {
#ifdef SQLITE_ENABLE_LOCKING_STYLE
"ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE),
#endif
+#if SQLITE_ENABLE_MATH_FUNCTIONS
+ "ENABLE_MATH_FUNCTIONS",
+#endif
#if SQLITE_ENABLE_MEMORY_MANAGEMENT
"ENABLE_MEMORY_MANAGEMENT",
#endif
@@ -302,6 +309,9 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_ENABLE_NULL_TRIM
"ENABLE_NULL_TRIM",
#endif
+#if SQLITE_ENABLE_OFFSET_SQL_FUNC
+ "ENABLE_OFFSET_SQL_FUNC",
+#endif
#if SQLITE_ENABLE_OVERSIZE_CELL_CHECK
"ENABLE_OVERSIZE_CELL_CHECK",
#endif
@@ -332,7 +342,7 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_ENABLE_SQLLOG
"ENABLE_SQLLOG",
#endif
-#if defined(SQLITE_ENABLE_STAT4)
+#if SQLITE_ENABLE_STAT4
"ENABLE_STAT4",
#endif
#if SQLITE_ENABLE_STMTVTAB
@@ -386,8 +396,10 @@ static const char * const sqlite3azCompileOpt[] = {
#if HAVE_ISNAN || SQLITE_HAVE_ISNAN
"HAVE_ISNAN",
#endif
-#if SQLITE_HOMEGROWN_RECURSIVE_MUTEX
- "HOMEGROWN_RECURSIVE_MUTEX",
+#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX
+# if SQLITE_HOMEGROWN_RECURSIVE_MUTEX != 1
+ "HOMEGROWN_RECURSIVE_MUTEX=" CTIMEOPT_VAL(SQLITE_HOMEGROWN_RECURSIVE_MUTEX),
+# endif
#endif
#if SQLITE_IGNORE_AFP_LOCK_ERRORS
"IGNORE_AFP_LOCK_ERRORS",
@@ -485,9 +497,6 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_MUTEX_NOOP
"MUTEX_NOOP",
#endif
-#if SQLITE_MUTEX_NREF
- "MUTEX_NREF",
-#endif
#if SQLITE_MUTEX_OMIT
"MUTEX_OMIT",
#endif
@@ -557,7 +566,7 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_OMIT_CTE
"OMIT_CTE",
#endif
-#if SQLITE_OMIT_DATETIME_FUNCS
+#if defined(SQLITE_OMIT_DATETIME_FUNCS) || defined(SQLITE_OMIT_FLOATING_POINT)
"OMIT_DATETIME_FUNCS",
#endif
#if SQLITE_OMIT_DECLTYPE
@@ -566,6 +575,9 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_OMIT_DEPRECATED
"OMIT_DEPRECATED",
#endif
+#if SQLITE_OMIT_DESERIALIZE
+ "OMIT_DESERIALIZE",
+#endif
#if SQLITE_OMIT_DISKIO
"OMIT_DISKIO",
#endif
@@ -593,6 +605,9 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_OMIT_INTEGRITY_CHECK
"OMIT_INTEGRITY_CHECK",
#endif
+#if SQLITE_OMIT_INTROSPECTION_PRAGMAS
+ "OMIT_INTROSPECTION_PRAGMAS",
+#endif
#if SQLITE_OMIT_LIKE_OPTIMIZATION
"OMIT_LIKE_OPTIMIZATION",
#endif
@@ -656,8 +671,10 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_OMIT_TEST_CONTROL
"OMIT_TEST_CONTROL",
#endif
-#if SQLITE_OMIT_TRACE
- "OMIT_TRACE",
+#ifdef SQLITE_OMIT_TRACE
+# if SQLITE_OMIT_TRACE != 1
+ "OMIT_TRACE=" CTIMEOPT_VAL(SQLITE_OMIT_TRACE),
+# endif
#endif
#if SQLITE_OMIT_TRIGGER
"OMIT_TRIGGER",
@@ -692,8 +709,10 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_PERFORMANCE_TRACE
"PERFORMANCE_TRACE",
#endif
-#if SQLITE_POWERSAFE_OVERWRITE
- "POWERSAFE_OVERWRITE",
+#ifdef SQLITE_POWERSAFE_OVERWRITE
+# if SQLITE_POWERSAFE_OVERWRITE != 1
+ "POWERSAFE_OVERWRITE=" CTIMEOPT_VAL(SQLITE_POWERSAFE_OVERWRITE),
+# endif
#endif
#if SQLITE_PREFER_PROXY_LOCKING
"PREFER_PROXY_LOCKING",
@@ -728,7 +747,10 @@ static const char * const sqlite3azCompileOpt[] = {
#if SQLITE_SUBSTR_COMPATIBILITY
"SUBSTR_COMPATIBILITY",
#endif
-#if SQLITE_SYSTEM_MALLOC
+#if (!defined(SQLITE_WIN32_MALLOC) \
+ && !defined(SQLITE_ZERO_MALLOC) \
+ && !defined(SQLITE_MEMDEBUG) \
+ ) || defined(SQLITE_SYSTEM_MALLOC)
"SYSTEM_MALLOC",
#endif
#if SQLITE_TCL
@@ -990,6 +1012,18 @@ SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){
# define MSVC_VERSION 0
#endif
+/*
+** Some C99 functions in "math.h" are only present for MSVC when its version
+** is associated with Visual Studio 2013 or higher.
+*/
+#ifndef SQLITE_HAVE_C99_MATH_FUNCS
+# if MSVC_VERSION==0 || MSVC_VERSION>=1800
+# define SQLITE_HAVE_C99_MATH_FUNCS (1)
+# else
+# define SQLITE_HAVE_C99_MATH_FUNCS (0)
+# endif
+#endif
+
/* Needed for various definitions... */
#if defined(__GNUC__) && !defined(_GNU_SOURCE)
# define _GNU_SOURCE
@@ -1171,9 +1205,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.33.0"
-#define SQLITE_VERSION_NUMBER 3033000
-#define SQLITE_SOURCE_ID "2020-08-14 13:23:32 fca8dc8b578f215a969cd899336378966156154710873e68b3d9ac5881b0ff3f"
+#define SQLITE_VERSION "3.36.0"
+#define SQLITE_VERSION_NUMBER 3036000
+#define SQLITE_SOURCE_ID "2021-06-18 18:36:39 5c9a6c06871cb9fe42814af9c039eb6da5427a6ec28f187af7ebfb62eafa66e5"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -1552,6 +1586,7 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8))
#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8))
#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8))
+#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
@@ -2175,6 +2210,23 @@ struct sqlite3_io_methods {
** file to the database file, but before the *-shm file is updated to
** record the fact that the pages have been checkpointed.
** </ul>
+**
+** <li>[[SQLITE_FCNTL_EXTERNAL_READER]]
+** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect
+** whether or not there is a database client in another process with a wal-mode
+** transaction open on the database or not. It is only available on unix.The
+** (void*) argument passed with this file-control should be a pointer to a
+** value of type (int). The integer value is set to 1 if the database is a wal
+** mode database and there exists at least one client in another process that
+** currently has an SQL transaction open on the database. It is set to 0 if
+** the database is not a wal-mode db, or if there is no such connection in any
+** other process. This opcode cannot be used to detect transactions opened
+** by clients within the current process, only within other processes.
+** </ul>
+**
+** <li>[[SQLITE_FCNTL_CKSM_FILE]]
+** Used by the cksmvfs VFS module only.
+** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
#define SQLITE_FCNTL_GET_LOCKPROXYFILE 2
@@ -2214,6 +2266,8 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CKPT_DONE 37
#define SQLITE_FCNTL_RESERVE_BYTES 38
#define SQLITE_FCNTL_CKPT_START 39
+#define SQLITE_FCNTL_EXTERNAL_READER 40
+#define SQLITE_FCNTL_CKSM_FILE 41
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -3162,7 +3216,13 @@ struct sqlite3_mem_methods {
** The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether triggers are disabled or enabled
** following this call. The second parameter may be a NULL pointer, in
-** which case the trigger setting is not reported back. </dd>
+** which case the trigger setting is not reported back.
+**
+** <p>Originally this option disabled all triggers. ^(However, since
+** SQLite version 3.35.0, TEMP triggers are still allowed even if
+** this option is off. So, in other words, this option now only disables
+** triggers in the main database schema or in the schemas of ATTACH-ed
+** databases.)^ </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt>
@@ -3173,7 +3233,13 @@ struct sqlite3_mem_methods {
** The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether views are disabled or enabled
** following this call. The second parameter may be a NULL pointer, in
-** which case the view setting is not reported back. </dd>
+** which case the view setting is not reported back.
+**
+** <p>Originally this option disabled all views. ^(However, since
+** SQLite version 3.35.0, TEMP views are still allowed even if
+** this option is off. So, in other words, this option now only disables
+** views in the main database schema or in the schemas of ATTACH-ed
+** databases.)^ </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
@@ -4546,6 +4612,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** that uses dot-files in place of posix advisory locking.
** <tr><td> file:data.db?mode=readonly <td>
** An error. "readonly" is not a valid option for the "mode" parameter.
+** Use "ro" instead: "file:data.db?mode=ro".
** </table>
**
** ^URI hexadecimal escape sequences (%HH) are supported within the path and
@@ -4744,7 +4811,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
** If the Y parameter to sqlite3_free_filename(Y) is anything other
** than a NULL pointer or a pointer previously acquired from
** sqlite3_create_filename(), then bad things such as heap
-** corruption or segfaults may occur. The value Y should be
+** corruption or segfaults may occur. The value Y should not be
** used again after sqlite3_free_filename(Y) has been called. This means
** that if the [sqlite3_vfs.xOpen()] method of a VFS has been called using Y,
** then the corresponding [sqlite3_module.xClose() method should also be
@@ -5213,6 +5280,15 @@ SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt);
** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and
** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so
** sqlite3_stmt_readonly() returns false for those commands.
+**
+** ^This routine returns false if there is any possibility that the
+** statement might change the database file. ^A false return does
+** not guarantee that the statement will change the database file.
+** ^For example, an UPDATE statement might have a WHERE clause that
+** makes it a no-op, but the sqlite3_stmt_readonly() result would still
+** be false. ^Similarly, a CREATE TABLE IF NOT EXISTS statement is a
+** read-only no-op if the table already exists, but
+** sqlite3_stmt_readonly() still returns false for such a statement.
*/
SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
@@ -5382,18 +5458,22 @@ typedef struct sqlite3_context sqlite3_context;
** contain embedded NULs. The result of expressions involving strings
** with embedded NULs is undefined.
**
-** ^The fifth argument to the BLOB and string binding interfaces
-** is a destructor used to dispose of the BLOB or
-** string after SQLite has finished with it. ^The destructor is called
-** to dispose of the BLOB or string even if the call to the bind API fails,
-** except the destructor is not called if the third parameter is a NULL
-** pointer or the fourth parameter is negative.
-** ^If the fifth argument is
-** the special value [SQLITE_STATIC], then SQLite assumes that the
-** information is in static, unmanaged space and does not need to be freed.
-** ^If the fifth argument has the value [SQLITE_TRANSIENT], then
-** SQLite makes its own private copy of the data immediately, before
-** the sqlite3_bind_*() routine returns.
+** ^The fifth argument to the BLOB and string binding interfaces controls
+** or indicates the lifetime of the object referenced by the third parameter.
+** These three options exist:
+** ^ (1) A destructor to dispose of the BLOB or string after SQLite has finished
+** with it may be passed. ^It is called to dispose of the BLOB or string even
+** if the call to the bind API fails, except the destructor is not called if
+** the third parameter is a NULL pointer or the fourth parameter is negative.
+** ^ (2) The special constant, [SQLITE_STATIC], may be passsed to indicate that
+** the application remains responsible for disposing of the object. ^In this
+** case, the object and the provided pointer to it must remain valid until
+** either the prepared statement is finalized or the same SQL parameter is
+** bound to something else, whichever occurs sooner.
+** ^ (3) The constant, [SQLITE_TRANSIENT], may be passed to indicate that the
+** object is to be copied prior to the return from sqlite3_bind_*(). ^The
+** object and pointer to it must remain valid until then. ^SQLite will then
+** manage the lifetime of its private copy.
**
** ^The sixth argument to sqlite3_bind_text64() must be one of
** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]
@@ -6135,7 +6215,6 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions,
** index expressions, or the WHERE clause of partial indexes.
**
-** <span style="background-color:#ffff90;">
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
** all application-defined SQL functions that do not need to be
** used inside of triggers, view, CHECK constraints, or other elements of
@@ -6145,7 +6224,6 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** a database file to include invocations of the function with parameters
** chosen by the attacker, which the application will then execute when
** the database file is opened and read.
-** </span>
**
** ^(The fifth parameter is an arbitrary pointer. The implementation of the
** function can gain access to this pointer using [sqlite3_user_data()].)^
@@ -7235,6 +7313,57 @@ SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName);
SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName);
/*
+** CAPI3REF: Determine the transaction state of a database
+** METHOD: sqlite3
+**
+** ^The sqlite3_txn_state(D,S) interface returns the current
+** [transaction state] of schema S in database connection D. ^If S is NULL,
+** then the highest transaction state of any schema on database connection D
+** is returned. Transaction states are (in order of lowest to highest):
+** <ol>
+** <li value="0"> SQLITE_TXN_NONE
+** <li value="1"> SQLITE_TXN_READ
+** <li value="2"> SQLITE_TXN_WRITE
+** </ol>
+** ^If the S argument to sqlite3_txn_state(D,S) is not the name of
+** a valid schema, then -1 is returned.
+*/
+SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
+
+/*
+** CAPI3REF: Allowed return values from [sqlite3_txn_state()]
+** KEYWORDS: {transaction state}
+**
+** These constants define the current transaction state of a database file.
+** ^The [sqlite3_txn_state(D,S)] interface returns one of these
+** constants in order to describe the transaction state of schema S
+** in [database connection] D.
+**
+** <dl>
+** [[SQLITE_TXN_NONE]] <dt>SQLITE_TXN_NONE</dt>
+** <dd>The SQLITE_TXN_NONE state means that no transaction is currently
+** pending.</dd>
+**
+** [[SQLITE_TXN_READ]] <dt>SQLITE_TXN_READ</dt>
+** <dd>The SQLITE_TXN_READ state means that the database is currently
+** in a read transaction. Content has been read from the database file
+** but nothing in the database file has changed. The transaction state
+** will advanced to SQLITE_TXN_WRITE if any changes occur and there are
+** no other conflicting concurrent write transactions. The transaction
+** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
+** [COMMIT].</dd>
+**
+** [[SQLITE_TXN_WRITE]] <dt>SQLITE_TXN_WRITE</dt>
+** <dd>The SQLITE_TXN_WRITE state means that the database is currently
+** in a write transaction. Content has been written to the database file
+** but has not yet committed. The transaction state will change to
+** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
+*/
+#define SQLITE_TXN_NONE 0
+#define SQLITE_TXN_READ 1
+#define SQLITE_TXN_WRITE 2
+
+/*
** CAPI3REF: Find the next prepared statement
** METHOD: sqlite3
**
@@ -8760,7 +8889,10 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_RESULT_INTREAL 27
#define SQLITE_TESTCTRL_PRNG_SEED 28
#define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29
-#define SQLITE_TESTCTRL_LAST 29 /* Largest TESTCTRL */
+#define SQLITE_TESTCTRL_SEEK_COUNT 30
+#define SQLITE_TESTCTRL_TRACEFLAGS 31
+#define SQLITE_TESTCTRL_TUNE 32
+#define SQLITE_TESTCTRL_LAST 32 /* Largest TESTCTRL */
/*
** CAPI3REF: SQL Keyword Checking
@@ -10240,10 +10372,11 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE
**
** If the sqlite3_vtab_nochange(X) routine is called within the [xColumn]
-** method of a [virtual table], then it returns true if and only if the
+** method of a [virtual table], then it might return true if the
** column is being fetched as part of an UPDATE operation during which the
-** column value will not change. Applications might use this to substitute
-** a return value that is less expensive to compute and that the corresponding
+** column value will not change. The virtual table implementation can use
+** this hint as permission to substitute a return value that is less
+** expensive to compute and that the corresponding
** [xUpdate] method understands as a "no-change" value.
**
** If the [xColumn] method calls sqlite3_vtab_nochange() and finds that
@@ -10252,6 +10385,12 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** any of the [sqlite3_result_int|sqlite3_result_xxxxx() interfaces].
** In that case, [sqlite3_value_nochange(X)] will return true for the
** same column in the [xUpdate] method.
+**
+** The sqlite3_vtab_nochange() routine is an optimization. Virtual table
+** implementations should continue to give a correct answer even if the
+** sqlite3_vtab_nochange() interface were to always return false. In the
+** current implementation, the sqlite3_vtab_nochange() interface does always
+** returns false for the enhanced [UPDATE FROM] statement.
*/
SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*);
@@ -10393,6 +10532,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
/*
** CAPI3REF: Flush caches to disk mid-transaction
+** METHOD: sqlite3
**
** ^If a write-transaction is open on [database connection] D when the
** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
@@ -10425,6 +10565,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
/*
** CAPI3REF: The pre-update hook.
+** METHOD: sqlite3
**
** ^These interfaces are only available if SQLite is compiled using the
** [SQLITE_ENABLE_PREUPDATE_HOOK] compile-time option.
@@ -10465,7 +10606,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** seventh parameter is the final rowid value of the row being inserted
** or updated. The value of the seventh parameter passed to the callback
** function is not defined for operations on WITHOUT ROWID tables, or for
-** INSERT operations on rowid tables.
+** DELETE operations on rowid tables.
**
** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()],
** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces
@@ -10503,6 +10644,15 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** triggers; or 2 for changes resulting from triggers called by top-level
** triggers; and so forth.
**
+** When the [sqlite3_blob_write()] API is used to update a blob column,
+** the pre-update hook is invoked with SQLITE_DELETE. This is because the
+** in this case the new values are not available. In this case, when a
+** callback made with op==SQLITE_DELETE is actuall a write using the
+** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
+** the index of the column being written. In other cases, where the
+** pre-update hook is being invoked for some other reason, including a
+** regular DELETE, sqlite3_preupdate_blobwrite() returns -1.
+**
** See also: [sqlite3_update_hook()]
*/
#if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
@@ -10523,10 +10673,12 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
SQLITE_API int sqlite3_preupdate_count(sqlite3 *);
SQLITE_API int sqlite3_preupdate_depth(sqlite3 *);
SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **);
+SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *);
#endif
/*
** CAPI3REF: Low-level system error code
+** METHOD: sqlite3
**
** ^Attempt to return the underlying operating system error code or error
** number that caused the most recent I/O error or failure to open a file.
@@ -10760,8 +10912,8 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory
** allocation error occurs.
**
-** This interface is only available if SQLite is compiled with the
-** [SQLITE_ENABLE_DESERIALIZE] option.
+** This interface is omitted if SQLite is compiled with the
+** [SQLITE_OMIT_DESERIALIZE] option.
*/
SQLITE_API unsigned char *sqlite3_serialize(
sqlite3 *db, /* The database connection */
@@ -10812,8 +10964,8 @@ SQLITE_API unsigned char *sqlite3_serialize(
** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then
** [sqlite3_free()] is invoked on argument P prior to returning.
**
-** This interface is only available if SQLite is compiled with the
-** [SQLITE_ENABLE_DESERIALIZE] option.
+** This interface is omitted if SQLite is compiled with the
+** [SQLITE_OMIT_DESERIALIZE] option.
*/
SQLITE_API int sqlite3_deserialize(
sqlite3 *db, /* The database connection */
@@ -11062,6 +11214,38 @@ SQLITE_API int sqlite3session_create(
*/
SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
+/*
+** CAPIREF: Conigure a Session Object
+** METHOD: sqlite3_session
+**
+** This method is used to configure a session object after it has been
+** created. At present the only valid value for the second parameter is
+** [SQLITE_SESSION_OBJCONFIG_SIZE].
+**
+** Arguments for sqlite3session_object_config()
+**
+** The following values may passed as the the 4th parameter to
+** sqlite3session_object_config().
+**
+** <dt>SQLITE_SESSION_OBJCONFIG_SIZE <dd>
+** This option is used to set, clear or query the flag that enables
+** the [sqlite3session_changeset_size()] API. Because it imposes some
+** computational overhead, this API is disabled by default. Argument
+** pArg must point to a value of type (int). If the value is initially
+** 0, then the sqlite3session_changeset_size() API is disabled. If it
+** is greater than 0, then the same API is enabled. Or, if the initial
+** value is less than zero, no change is made. In all cases the (int)
+** variable is set to 1 if the sqlite3session_changeset_size() API is
+** enabled following the current call, or 0 otherwise.
+**
+** It is an error (SQLITE_MISUSE) to attempt to modify this setting after
+** the first table has been attached to the session object.
+*/
+SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg);
+
+/*
+*/
+#define SQLITE_SESSION_OBJCONFIG_SIZE 1
/*
** CAPI3REF: Enable Or Disable A Session Object
@@ -11307,6 +11491,22 @@ SQLITE_API int sqlite3session_changeset(
);
/*
+** CAPI3REF: Return An Upper-limit For The Size Of The Changeset
+** METHOD: sqlite3_session
+**
+** By default, this function always returns 0. For it to return
+** a useful result, the sqlite3_session object must have been configured
+** to enable this API using sqlite3session_object_config() with the
+** SQLITE_SESSION_OBJCONFIG_SIZE verb.
+**
+** When enabled, this function returns an upper limit, in bytes, for the size
+** of the changeset that might be produced if sqlite3session_changeset() were
+** called. The final changeset size might be equal to or smaller than the
+** size in bytes returned by this function.
+*/
+SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession);
+
+/*
** CAPI3REF: Load The Difference Between Tables Into A Session
** METHOD: sqlite3_session
**
@@ -11424,6 +11624,14 @@ SQLITE_API int sqlite3session_patchset(
SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession);
/*
+** CAPI3REF: Query for the amount of heap memory used by a session object.
+**
+** This API returns the total amount of heap memory in bytes currently
+** used by the session object passed as the only argument.
+*/
+SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession);
+
+/*
** CAPI3REF: Create An Iterator To Traverse A Changeset
** CONSTRUCTOR: sqlite3_changeset_iter
**
@@ -11525,18 +11733,23 @@ SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this
** is not the case, this function returns [SQLITE_MISUSE].
**
-** If argument pzTab is not NULL, then *pzTab is set to point to a
-** nul-terminated utf-8 encoded string containing the name of the table
-** affected by the current change. The buffer remains valid until either
-** sqlite3changeset_next() is called on the iterator or until the
-** conflict-handler function returns. If pnCol is not NULL, then *pnCol is
-** set to the number of columns in the table affected by the change. If
-** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change
+** Arguments pOp, pnCol and pzTab may not be NULL. Upon return, three
+** outputs are set through these pointers:
+**
+** *pOp is set to one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE],
+** depending on the type of change that the iterator currently points to;
+**
+** *pnCol is set to the number of columns in the table affected by the change; and
+**
+** *pzTab is set to point to a nul-terminated utf-8 encoded string containing
+** the name of the table affected by the current change. The buffer remains
+** valid until either sqlite3changeset_next() is called on the iterator
+** or until the conflict-handler function returns.
+**
+** If pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change
** is an indirect change, or false (0) otherwise. See the documentation for
** [sqlite3session_indirect()] for a description of direct and indirect
-** changes. Finally, if pOp is not NULL, then *pOp is set to one of
-** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the
-** type of change that the iterator currently points to.
+** changes.
**
** If no error occurs, SQLITE_OK is returned. If an error does occur, an
** SQLite error code is returned. The values of the output variables may not
@@ -13297,11 +13510,7 @@ struct fts5_api {
** The maximum depth of an expression tree. This is limited to
** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might
** want to place more severe limits on the complexity of an
-** expression.
-**
-** A value of 0 used to mean that the limit was not enforced.
-** But that is no longer true. The limit is now strictly enforced
-** at all times.
+** expression. A value of 0 means that there is no limit.
*/
#ifndef SQLITE_MAX_EXPR_DEPTH
# define SQLITE_MAX_EXPR_DEPTH 1000
@@ -13469,7 +13678,8 @@ struct fts5_api {
#ifndef __has_extension
# define __has_extension(x) 0 /* compatibility with non-clang compilers */
#endif
-#if GCC_VERSION>=4007000 || __has_extension(c_atomic)
+#if GCC_VERSION>=4007000 || \
+ (__has_extension(c_atomic) && __has_extension(c_atomic_store_n))
# define AtomicLoad(PTR) __atomic_load_n((PTR),__ATOMIC_RELAXED)
# define AtomicStore(PTR,VAL) __atomic_store_n((PTR),(VAL),__ATOMIC_RELAXED)
#else
@@ -14036,90 +14246,93 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*);
#define TK_TIES 94
#define TK_GENERATED 95
#define TK_ALWAYS 96
-#define TK_REINDEX 97
-#define TK_RENAME 98
-#define TK_CTIME_KW 99
-#define TK_ANY 100
-#define TK_BITAND 101
-#define TK_BITOR 102
-#define TK_LSHIFT 103
-#define TK_RSHIFT 104
-#define TK_PLUS 105
-#define TK_MINUS 106
-#define TK_STAR 107
-#define TK_SLASH 108
-#define TK_REM 109
-#define TK_CONCAT 110
-#define TK_COLLATE 111
-#define TK_BITNOT 112
-#define TK_ON 113
-#define TK_INDEXED 114
-#define TK_STRING 115
-#define TK_JOIN_KW 116
-#define TK_CONSTRAINT 117
-#define TK_DEFAULT 118
-#define TK_NULL 119
-#define TK_PRIMARY 120
-#define TK_UNIQUE 121
-#define TK_CHECK 122
-#define TK_REFERENCES 123
-#define TK_AUTOINCR 124
-#define TK_INSERT 125
-#define TK_DELETE 126
-#define TK_UPDATE 127
-#define TK_SET 128
-#define TK_DEFERRABLE 129
-#define TK_FOREIGN 130
-#define TK_DROP 131
-#define TK_UNION 132
-#define TK_ALL 133
-#define TK_EXCEPT 134
-#define TK_INTERSECT 135
-#define TK_SELECT 136
-#define TK_VALUES 137
-#define TK_DISTINCT 138
-#define TK_DOT 139
-#define TK_FROM 140
-#define TK_JOIN 141
-#define TK_USING 142
-#define TK_ORDER 143
-#define TK_GROUP 144
-#define TK_HAVING 145
-#define TK_LIMIT 146
-#define TK_WHERE 147
-#define TK_INTO 148
-#define TK_NOTHING 149
-#define TK_FLOAT 150
-#define TK_BLOB 151
-#define TK_INTEGER 152
-#define TK_VARIABLE 153
-#define TK_CASE 154
-#define TK_WHEN 155
-#define TK_THEN 156
-#define TK_ELSE 157
-#define TK_INDEX 158
-#define TK_ALTER 159
-#define TK_ADD 160
-#define TK_WINDOW 161
-#define TK_OVER 162
-#define TK_FILTER 163
-#define TK_COLUMN 164
-#define TK_AGG_FUNCTION 165
-#define TK_AGG_COLUMN 166
-#define TK_TRUEFALSE 167
-#define TK_ISNOT 168
-#define TK_FUNCTION 169
-#define TK_UMINUS 170
-#define TK_UPLUS 171
-#define TK_TRUTH 172
-#define TK_REGISTER 173
-#define TK_VECTOR 174
-#define TK_SELECT_COLUMN 175
-#define TK_IF_NULL_ROW 176
-#define TK_ASTERISK 177
-#define TK_SPAN 178
-#define TK_SPACE 179
-#define TK_ILLEGAL 180
+#define TK_MATERIALIZED 97
+#define TK_REINDEX 98
+#define TK_RENAME 99
+#define TK_CTIME_KW 100
+#define TK_ANY 101
+#define TK_BITAND 102
+#define TK_BITOR 103
+#define TK_LSHIFT 104
+#define TK_RSHIFT 105
+#define TK_PLUS 106
+#define TK_MINUS 107
+#define TK_STAR 108
+#define TK_SLASH 109
+#define TK_REM 110
+#define TK_CONCAT 111
+#define TK_COLLATE 112
+#define TK_BITNOT 113
+#define TK_ON 114
+#define TK_INDEXED 115
+#define TK_STRING 116
+#define TK_JOIN_KW 117
+#define TK_CONSTRAINT 118
+#define TK_DEFAULT 119
+#define TK_NULL 120
+#define TK_PRIMARY 121
+#define TK_UNIQUE 122
+#define TK_CHECK 123
+#define TK_REFERENCES 124
+#define TK_AUTOINCR 125
+#define TK_INSERT 126
+#define TK_DELETE 127
+#define TK_UPDATE 128
+#define TK_SET 129
+#define TK_DEFERRABLE 130
+#define TK_FOREIGN 131
+#define TK_DROP 132
+#define TK_UNION 133
+#define TK_ALL 134
+#define TK_EXCEPT 135
+#define TK_INTERSECT 136
+#define TK_SELECT 137
+#define TK_VALUES 138
+#define TK_DISTINCT 139
+#define TK_DOT 140
+#define TK_FROM 141
+#define TK_JOIN 142
+#define TK_USING 143
+#define TK_ORDER 144
+#define TK_GROUP 145
+#define TK_HAVING 146
+#define TK_LIMIT 147
+#define TK_WHERE 148
+#define TK_RETURNING 149
+#define TK_INTO 150
+#define TK_NOTHING 151
+#define TK_FLOAT 152
+#define TK_BLOB 153
+#define TK_INTEGER 154
+#define TK_VARIABLE 155
+#define TK_CASE 156
+#define TK_WHEN 157
+#define TK_THEN 158
+#define TK_ELSE 159
+#define TK_INDEX 160
+#define TK_ALTER 161
+#define TK_ADD 162
+#define TK_WINDOW 163
+#define TK_OVER 164
+#define TK_FILTER 165
+#define TK_COLUMN 166
+#define TK_AGG_FUNCTION 167
+#define TK_AGG_COLUMN 168
+#define TK_TRUEFALSE 169
+#define TK_ISNOT 170
+#define TK_FUNCTION 171
+#define TK_UMINUS 172
+#define TK_UPLUS 173
+#define TK_TRUTH 174
+#define TK_REGISTER 175
+#define TK_VECTOR 176
+#define TK_SELECT_COLUMN 177
+#define TK_IF_NULL_ROW 178
+#define TK_ASTERISK 179
+#define TK_SPAN 180
+#define TK_ERROR 181
+#define TK_SPACE 182
+#define TK_ILLEGAL 183
/************** End of parse.h ***********************************************/
/************** Continuing where we left off in sqliteInt.h ******************/
@@ -14535,15 +14748,14 @@ typedef INT16_TYPE LogEst;
** SELECTTRACE_ENABLED will be either 1 or 0 depending on whether or not
** the Select query generator tracing logic is turned on.
*/
-#if defined(SQLITE_ENABLE_SELECTTRACE)
-# define SELECTTRACE_ENABLED 1
-#else
-# define SELECTTRACE_ENABLED 0
+#if !defined(SQLITE_AMALGAMATION)
+SQLITE_PRIVATE u32 sqlite3SelectTrace;
#endif
-#if defined(SQLITE_ENABLE_SELECTTRACE)
+#if defined(SQLITE_DEBUG) \
+ && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE))
# define SELECTTRACE_ENABLED 1
# define SELECTTRACE(K,P,S,X) \
- if(sqlite3_unsupported_selecttrace&(K)) \
+ if(sqlite3SelectTrace&(K)) \
sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\
sqlite3DebugPrintf X
#else
@@ -14552,6 +14764,19 @@ typedef INT16_TYPE LogEst;
#endif
/*
+** Macros for "wheretrace"
+*/
+SQLITE_PRIVATE u32 sqlite3WhereTrace;
+#if defined(SQLITE_DEBUG) \
+ && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHERETRACE))
+# define WHERETRACE(K,X) if(sqlite3WhereTrace&(K)) sqlite3DebugPrintf X
+# define WHERETRACE_ENABLED 1
+#else
+# define WHERETRACE(K,X)
+#endif
+
+
+/*
** An instance of the following structure is used to store the busy-handler
** callback for a given sqlite handle.
**
@@ -14662,7 +14887,10 @@ typedef struct AutoincInfo AutoincInfo;
typedef struct Bitvec Bitvec;
typedef struct CollSeq CollSeq;
typedef struct Column Column;
+typedef struct Cte Cte;
+typedef struct CteUse CteUse;
typedef struct Db Db;
+typedef struct DbFixer DbFixer;
typedef struct Schema Schema;
typedef struct Expr Expr;
typedef struct ExprList ExprList;
@@ -14680,14 +14908,17 @@ typedef struct LookasideSlot LookasideSlot;
typedef struct Module Module;
typedef struct NameContext NameContext;
typedef struct Parse Parse;
+typedef struct ParseCleanup ParseCleanup;
typedef struct PreUpdate PreUpdate;
typedef struct PrintfArguments PrintfArguments;
typedef struct RenameToken RenameToken;
+typedef struct Returning Returning;
typedef struct RowSet RowSet;
typedef struct Savepoint Savepoint;
typedef struct Select Select;
typedef struct SQLiteThread SQLiteThread;
typedef struct SelectDest SelectDest;
+typedef struct SrcItem SrcItem;
typedef struct SrcList SrcList;
typedef struct sqlite3_str StrAccum; /* Internal alias for sqlite3_str */
typedef struct Table Table;
@@ -15082,16 +15313,24 @@ SQLITE_PRIVATE int sqlite3BtreeCommit(Btree*);
SQLITE_PRIVATE int sqlite3BtreeRollback(Btree*,int,int);
SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree*,int);
SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree*, Pgno*, int flags);
-SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree*);
-SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree*);
+SQLITE_PRIVATE int sqlite3BtreeTxnState(Btree*);
SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree*);
+
SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *, int, void(*)(void *));
SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *pBtree);
#ifndef SQLITE_OMIT_SHARED_CACHE
SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *pBtree, int iTab, u8 isWriteLock);
#endif
+
+/* Savepoints are named, nestable SQL transactions mostly implemented */
+/* in vdbe.c and pager.c See https://sqlite.org/lang_savepoint.html */
SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *, int, int);
+/* "Checkpoint" only refers to WAL. See https://sqlite.org/wal.html#ckpt */
+#ifndef SQLITE_OMIT_WAL
+SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *);
+#endif
+
SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *);
SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *);
SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *, Btree *);
@@ -15251,6 +15490,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags);
#define BTREE_SAVEPOSITION 0x02 /* Leave cursor pointing at NEXT or PREV */
#define BTREE_AUXDELETE 0x04 /* not the primary delete operation */
#define BTREE_APPEND 0x08 /* Insert is likely an append */
+#define BTREE_PREFORMAT 0x80 /* Inserted data is a preformated cell */
/* An instance of the BtreePayload object describes the content of a single
** entry in either an index or table btree.
@@ -15328,6 +15568,12 @@ SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask);
SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *pBt);
SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void);
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE sqlite3_uint64 sqlite3BtreeSeekCount(Btree*);
+#else
+# define sqlite3BtreeSeekCount(X) 0
+#endif
+
#ifndef NDEBUG
SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor*);
#endif
@@ -15344,6 +15590,8 @@ SQLITE_PRIVATE void sqlite3BtreeCursorList(Btree*);
SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *);
#endif
+SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor*, BtCursor*, i64);
+
/*
** If we are not using shared cache, then there is no need to
** use mutexes to access the BtShared structures. So make the
@@ -15621,7 +15869,7 @@ typedef struct VdbeOpList VdbeOpList;
#define OP_Le 55 /* jump, same as TK_LE, synopsis: IF r[P3]<=r[P1] */
#define OP_Lt 56 /* jump, same as TK_LT, synopsis: IF r[P3]<r[P1] */
#define OP_Ge 57 /* jump, same as TK_GE, synopsis: IF r[P3]>=r[P1] */
-#define OP_ElseNotEq 58 /* jump, same as TK_ESCAPE */
+#define OP_ElseEq 58 /* jump, same as TK_ESCAPE */
#define OP_DecrJumpZero 59 /* jump, synopsis: if (--r[P1])==0 goto P2 */
#define OP_IncrVacuum 60 /* jump */
#define OP_VNext 61 /* jump */
@@ -15643,102 +15891,106 @@ typedef struct VdbeOpList VdbeOpList;
#define OP_Copy 77 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */
#define OP_SCopy 78 /* synopsis: r[P2]=r[P1] */
#define OP_IntCopy 79 /* synopsis: r[P2]=r[P1] */
-#define OP_ResultRow 80 /* synopsis: output=r[P1@P2] */
-#define OP_CollSeq 81
-#define OP_AddImm 82 /* synopsis: r[P1]=r[P1]+P2 */
-#define OP_RealAffinity 83
-#define OP_Cast 84 /* synopsis: affinity(r[P1]) */
-#define OP_Permutation 85
-#define OP_Compare 86 /* synopsis: r[P1@P3] <-> r[P2@P3] */
-#define OP_IsTrue 87 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */
-#define OP_Offset 88 /* synopsis: r[P3] = sqlite_offset(P1) */
-#define OP_Column 89 /* synopsis: r[P3]=PX */
-#define OP_Affinity 90 /* synopsis: affinity(r[P1@P2]) */
-#define OP_MakeRecord 91 /* synopsis: r[P3]=mkrec(r[P1@P2]) */
-#define OP_Count 92 /* synopsis: r[P2]=count() */
-#define OP_ReadCookie 93
-#define OP_SetCookie 94
-#define OP_ReopenIdx 95 /* synopsis: root=P2 iDb=P3 */
-#define OP_OpenRead 96 /* synopsis: root=P2 iDb=P3 */
-#define OP_OpenWrite 97 /* synopsis: root=P2 iDb=P3 */
-#define OP_OpenDup 98
-#define OP_OpenAutoindex 99 /* synopsis: nColumn=P2 */
-#define OP_OpenEphemeral 100 /* synopsis: nColumn=P2 */
-#define OP_BitAnd 101 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */
-#define OP_BitOr 102 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */
-#define OP_ShiftLeft 103 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<<r[P1] */
-#define OP_ShiftRight 104 /* same as TK_RSHIFT, synopsis: r[P3]=r[P2]>>r[P1] */
-#define OP_Add 105 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */
-#define OP_Subtract 106 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */
-#define OP_Multiply 107 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */
-#define OP_Divide 108 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */
-#define OP_Remainder 109 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */
-#define OP_Concat 110 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */
-#define OP_SorterOpen 111
-#define OP_BitNot 112 /* same as TK_BITNOT, synopsis: r[P2]= ~r[P1] */
-#define OP_SequenceTest 113 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */
-#define OP_OpenPseudo 114 /* synopsis: P3 columns in r[P2] */
-#define OP_String8 115 /* same as TK_STRING, synopsis: r[P2]='P4' */
-#define OP_Close 116
-#define OP_ColumnsUsed 117
-#define OP_SeekHit 118 /* synopsis: seekHit=P2 */
-#define OP_Sequence 119 /* synopsis: r[P2]=cursor[P1].ctr++ */
-#define OP_NewRowid 120 /* synopsis: r[P2]=rowid */
-#define OP_Insert 121 /* synopsis: intkey=r[P3] data=r[P2] */
-#define OP_Delete 122
-#define OP_ResetCount 123
-#define OP_SorterCompare 124 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */
-#define OP_SorterData 125 /* synopsis: r[P2]=data */
-#define OP_RowData 126 /* synopsis: r[P2]=data */
-#define OP_Rowid 127 /* synopsis: r[P2]=rowid */
-#define OP_NullRow 128
-#define OP_SeekEnd 129
-#define OP_IdxInsert 130 /* synopsis: key=r[P2] */
-#define OP_SorterInsert 131 /* synopsis: key=r[P2] */
-#define OP_IdxDelete 132 /* synopsis: key=r[P2@P3] */
-#define OP_DeferredSeek 133 /* synopsis: Move P3 to P1.rowid if needed */
-#define OP_IdxRowid 134 /* synopsis: r[P2]=rowid */
-#define OP_FinishSeek 135
-#define OP_Destroy 136
-#define OP_Clear 137
-#define OP_ResetSorter 138
-#define OP_CreateBtree 139 /* synopsis: r[P2]=root iDb=P1 flags=P3 */
-#define OP_SqlExec 140
-#define OP_ParseSchema 141
-#define OP_LoadAnalysis 142
-#define OP_DropTable 143
-#define OP_DropIndex 144
-#define OP_DropTrigger 145
-#define OP_IntegrityCk 146
-#define OP_RowSetAdd 147 /* synopsis: rowset(P1)=r[P2] */
-#define OP_Param 148
-#define OP_FkCounter 149 /* synopsis: fkctr[P1]+=P2 */
-#define OP_Real 150 /* same as TK_FLOAT, synopsis: r[P2]=P4 */
-#define OP_MemMax 151 /* synopsis: r[P1]=max(r[P1],r[P2]) */
-#define OP_OffsetLimit 152 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */
-#define OP_AggInverse 153 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */
-#define OP_AggStep 154 /* synopsis: accum=r[P3] step(r[P2@P5]) */
-#define OP_AggStep1 155 /* synopsis: accum=r[P3] step(r[P2@P5]) */
-#define OP_AggValue 156 /* synopsis: r[P3]=value N=P2 */
-#define OP_AggFinal 157 /* synopsis: accum=r[P1] N=P2 */
-#define OP_Expire 158
-#define OP_CursorLock 159
-#define OP_CursorUnlock 160
-#define OP_TableLock 161 /* synopsis: iDb=P1 root=P2 write=P3 */
-#define OP_VBegin 162
-#define OP_VCreate 163
-#define OP_VDestroy 164
-#define OP_VOpen 165
-#define OP_VColumn 166 /* synopsis: r[P3]=vcolumn(P2) */
-#define OP_VRename 167
-#define OP_Pagecount 168
-#define OP_MaxPgcnt 169
-#define OP_Trace 170
-#define OP_CursorHint 171
-#define OP_ReleaseReg 172 /* synopsis: release r[P1@P2] mask P3 */
-#define OP_Noop 173
-#define OP_Explain 174
-#define OP_Abortable 175
+#define OP_ChngCntRow 80 /* synopsis: output=r[P1] */
+#define OP_ResultRow 81 /* synopsis: output=r[P1@P2] */
+#define OP_CollSeq 82
+#define OP_AddImm 83 /* synopsis: r[P1]=r[P1]+P2 */
+#define OP_RealAffinity 84
+#define OP_Cast 85 /* synopsis: affinity(r[P1]) */
+#define OP_Permutation 86
+#define OP_Compare 87 /* synopsis: r[P1@P3] <-> r[P2@P3] */
+#define OP_IsTrue 88 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */
+#define OP_ZeroOrNull 89 /* synopsis: r[P2] = 0 OR NULL */
+#define OP_Offset 90 /* synopsis: r[P3] = sqlite_offset(P1) */
+#define OP_Column 91 /* synopsis: r[P3]=PX */
+#define OP_Affinity 92 /* synopsis: affinity(r[P1@P2]) */
+#define OP_MakeRecord 93 /* synopsis: r[P3]=mkrec(r[P1@P2]) */
+#define OP_Count 94 /* synopsis: r[P2]=count() */
+#define OP_ReadCookie 95
+#define OP_SetCookie 96
+#define OP_ReopenIdx 97 /* synopsis: root=P2 iDb=P3 */
+#define OP_OpenRead 98 /* synopsis: root=P2 iDb=P3 */
+#define OP_OpenWrite 99 /* synopsis: root=P2 iDb=P3 */
+#define OP_OpenDup 100
+#define OP_OpenAutoindex 101 /* synopsis: nColumn=P2 */
+#define OP_BitAnd 102 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */
+#define OP_BitOr 103 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */
+#define OP_ShiftLeft 104 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<<r[P1] */
+#define OP_ShiftRight 105 /* same as TK_RSHIFT, synopsis: r[P3]=r[P2]>>r[P1] */
+#define OP_Add 106 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */
+#define OP_Subtract 107 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */
+#define OP_Multiply 108 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */
+#define OP_Divide 109 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */
+#define OP_Remainder 110 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */
+#define OP_Concat 111 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */
+#define OP_OpenEphemeral 112 /* synopsis: nColumn=P2 */
+#define OP_BitNot 113 /* same as TK_BITNOT, synopsis: r[P2]= ~r[P1] */
+#define OP_SorterOpen 114
+#define OP_SequenceTest 115 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */
+#define OP_String8 116 /* same as TK_STRING, synopsis: r[P2]='P4' */
+#define OP_OpenPseudo 117 /* synopsis: P3 columns in r[P2] */
+#define OP_Close 118
+#define OP_ColumnsUsed 119
+#define OP_SeekScan 120 /* synopsis: Scan-ahead up to P1 rows */
+#define OP_SeekHit 121 /* synopsis: set P2<=seekHit<=P3 */
+#define OP_Sequence 122 /* synopsis: r[P2]=cursor[P1].ctr++ */
+#define OP_NewRowid 123 /* synopsis: r[P2]=rowid */
+#define OP_Insert 124 /* synopsis: intkey=r[P3] data=r[P2] */
+#define OP_RowCell 125
+#define OP_Delete 126
+#define OP_ResetCount 127
+#define OP_SorterCompare 128 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */
+#define OP_SorterData 129 /* synopsis: r[P2]=data */
+#define OP_RowData 130 /* synopsis: r[P2]=data */
+#define OP_Rowid 131 /* synopsis: r[P2]=rowid */
+#define OP_NullRow 132
+#define OP_SeekEnd 133
+#define OP_IdxInsert 134 /* synopsis: key=r[P2] */
+#define OP_SorterInsert 135 /* synopsis: key=r[P2] */
+#define OP_IdxDelete 136 /* synopsis: key=r[P2@P3] */
+#define OP_DeferredSeek 137 /* synopsis: Move P3 to P1.rowid if needed */
+#define OP_IdxRowid 138 /* synopsis: r[P2]=rowid */
+#define OP_FinishSeek 139
+#define OP_Destroy 140
+#define OP_Clear 141
+#define OP_ResetSorter 142
+#define OP_CreateBtree 143 /* synopsis: r[P2]=root iDb=P1 flags=P3 */
+#define OP_SqlExec 144
+#define OP_ParseSchema 145
+#define OP_LoadAnalysis 146
+#define OP_DropTable 147
+#define OP_DropIndex 148
+#define OP_DropTrigger 149
+#define OP_IntegrityCk 150
+#define OP_RowSetAdd 151 /* synopsis: rowset(P1)=r[P2] */
+#define OP_Real 152 /* same as TK_FLOAT, synopsis: r[P2]=P4 */
+#define OP_Param 153
+#define OP_FkCounter 154 /* synopsis: fkctr[P1]+=P2 */
+#define OP_MemMax 155 /* synopsis: r[P1]=max(r[P1],r[P2]) */
+#define OP_OffsetLimit 156 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */
+#define OP_AggInverse 157 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */
+#define OP_AggStep 158 /* synopsis: accum=r[P3] step(r[P2@P5]) */
+#define OP_AggStep1 159 /* synopsis: accum=r[P3] step(r[P2@P5]) */
+#define OP_AggValue 160 /* synopsis: r[P3]=value N=P2 */
+#define OP_AggFinal 161 /* synopsis: accum=r[P1] N=P2 */
+#define OP_Expire 162
+#define OP_CursorLock 163
+#define OP_CursorUnlock 164
+#define OP_TableLock 165 /* synopsis: iDb=P1 root=P2 write=P3 */
+#define OP_VBegin 166
+#define OP_VCreate 167
+#define OP_VDestroy 168
+#define OP_VOpen 169
+#define OP_VColumn 170 /* synopsis: r[P3]=vcolumn(P2) */
+#define OP_VRename 171
+#define OP_Pagecount 172
+#define OP_MaxPgcnt 173
+#define OP_Trace 174
+#define OP_CursorHint 175
+#define OP_ReleaseReg 176 /* synopsis: release r[P1@P2] mask P3 */
+#define OP_Noop 177
+#define OP_Explain 178
+#define OP_Abortable 179
/* Properties such as "out2" or "jump" that are specified in
** comments following the "case" for each opcode in the vdbe.c
@@ -15761,21 +16013,21 @@ typedef struct VdbeOpList VdbeOpList;
/* 56 */ 0x0b, 0x0b, 0x01, 0x03, 0x01, 0x01, 0x01, 0x00,\
/* 64 */ 0x00, 0x02, 0x02, 0x08, 0x00, 0x10, 0x10, 0x10,\
/* 72 */ 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10,\
-/* 80 */ 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00, 0x12,\
-/* 88 */ 0x20, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00,\
-/* 96 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x26, 0x26,\
-/* 104 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x00,\
-/* 112 */ 0x12, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,\
-/* 120 */ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\
-/* 128 */ 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x10, 0x00,\
-/* 136 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\
-/* 144 */ 0x00, 0x00, 0x00, 0x06, 0x10, 0x00, 0x10, 0x04,\
-/* 152 */ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+/* 80 */ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,\
+/* 88 */ 0x12, 0x1e, 0x20, 0x00, 0x00, 0x00, 0x10, 0x10,\
+/* 96 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x26,\
+/* 104 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,\
+/* 112 */ 0x00, 0x12, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\
+/* 120 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,\
+/* 128 */ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x04, 0x04,\
+/* 136 */ 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x10,\
+/* 144 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,\
+/* 152 */ 0x10, 0x10, 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00,\
/* 160 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
-/* 168 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
-}
+/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00,\
+/* 176 */ 0x00, 0x00, 0x00, 0x00,}
-/* The sqlite3P2Values() routine is able to run faster if it knows
+/* The resolve3P2Values() routine is able to run faster if it knows
** the value of the largest JUMP opcode. The smaller the maximum
** JUMP opcode the better, so the mkopcodeh.tcl script that
** generated this include file strives to group all JUMP opcodes
@@ -15841,7 +16093,7 @@ SQLITE_PRIVATE void sqlite3ExplainBreakpoint(const char*,const char*);
#else
# define sqlite3ExplainBreakpoint(A,B) /*no-op*/
#endif
-SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*);
+SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe*, int, char*, u16);
SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe*, int addr, u8);
SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1);
SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2);
@@ -16308,6 +16560,12 @@ SQLITE_PRIVATE int sqlite3PCacheIsDirty(PCache *pCache);
# define SET_FULLSYNC(x,y)
#endif
+/* Maximum pathname length. Note: FILENAME_MAX defined by stdio.h
+*/
+#ifndef SQLITE_MAX_PATHLEN
+# define SQLITE_MAX_PATHLEN FILENAME_MAX
+#endif
+
/*
** The default size of a disk sector
*/
@@ -16813,6 +17071,11 @@ SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**);
#endif /* SQLITE_OMIT_DEPRECATED */
#define SQLITE_TRACE_NONLEGACY_MASK 0x0f /* Normal flags */
+/*
+** Maximum number of sqlite3.aDb[] entries. This is the number of attached
+** databases plus 2 for "main" and "temp".
+*/
+#define SQLITE_MAX_DB (SQLITE_MAX_ATTACHED+2)
/*
** Each database connection is an instance of the following structure.
@@ -16833,7 +17096,7 @@ struct sqlite3 {
int errCode; /* Most recent error code (SQLITE_*) */
int errMask; /* & result codes with this before returning */
int iSysErrno; /* Errno value from last system error */
- u16 dbOptFlags; /* Flags to enable/disable optimizations */
+ u32 dbOptFlags; /* Flags to enable/disable optimizations */
u8 enc; /* Text encoding */
u8 autoCommit; /* The auto-commit flag. */
u8 temp_store; /* 1: file 2: memory 0: default */
@@ -17040,24 +17303,26 @@ struct sqlite3 {
** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to
** selectively disable various optimizations.
*/
-#define SQLITE_QueryFlattener 0x0001 /* Query flattening */
-#define SQLITE_WindowFunc 0x0002 /* Use xInverse for window functions */
-#define SQLITE_GroupByOrder 0x0004 /* GROUPBY cover of ORDERBY */
-#define SQLITE_FactorOutConst 0x0008 /* Constant factoring */
-#define SQLITE_DistinctOpt 0x0010 /* DISTINCT using indexes */
-#define SQLITE_CoverIdxScan 0x0020 /* Covering index scans */
-#define SQLITE_OrderByIdxJoin 0x0040 /* ORDER BY of joins via index */
-#define SQLITE_Transitive 0x0080 /* Transitive constraints */
-#define SQLITE_OmitNoopJoin 0x0100 /* Omit unused tables in joins */
-#define SQLITE_CountOfView 0x0200 /* The count-of-view optimization */
-#define SQLITE_CursorHints 0x0400 /* Add OP_CursorHint opcodes */
-#define SQLITE_Stat4 0x0800 /* Use STAT4 data */
- /* TH3 expects the Stat4 ^^^^^^ value to be 0x0800. Don't change it */
-#define SQLITE_PushDown 0x1000 /* The push-down optimization */
-#define SQLITE_SimplifyJoin 0x2000 /* Convert LEFT JOIN to JOIN */
-#define SQLITE_SkipScan 0x4000 /* Skip-scans */
-#define SQLITE_PropagateConst 0x8000 /* The constant propagation opt */
-#define SQLITE_AllOpts 0xffff /* All optimizations */
+#define SQLITE_QueryFlattener 0x00000001 /* Query flattening */
+#define SQLITE_WindowFunc 0x00000002 /* Use xInverse for window functions */
+#define SQLITE_GroupByOrder 0x00000004 /* GROUPBY cover of ORDERBY */
+#define SQLITE_FactorOutConst 0x00000008 /* Constant factoring */
+#define SQLITE_DistinctOpt 0x00000010 /* DISTINCT using indexes */
+#define SQLITE_CoverIdxScan 0x00000020 /* Covering index scans */
+#define SQLITE_OrderByIdxJoin 0x00000040 /* ORDER BY of joins via index */
+#define SQLITE_Transitive 0x00000080 /* Transitive constraints */
+#define SQLITE_OmitNoopJoin 0x00000100 /* Omit unused tables in joins */
+#define SQLITE_CountOfView 0x00000200 /* The count-of-view optimization */
+#define SQLITE_CursorHints 0x00000400 /* Add OP_CursorHint opcodes */
+#define SQLITE_Stat4 0x00000800 /* Use STAT4 data */
+ /* TH3 expects this value ^^^^^^^^^^ to be 0x0000800. Don't change it */
+#define SQLITE_PushDown 0x00001000 /* The push-down optimization */
+#define SQLITE_SimplifyJoin 0x00002000 /* Convert LEFT JOIN to JOIN */
+#define SQLITE_SkipScan 0x00004000 /* Skip-scans */
+#define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */
+#define SQLITE_MinMaxOpt 0x00010000 /* The min/max optimization */
+#define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */
+#define SQLITE_AllOpts 0xffffffff /* All optimizations */
/*
** Macros for testing whether or not optimizations are enabled or disabled.
@@ -17213,6 +17478,9 @@ struct FuncDestructor {
** a single query. The iArg is ignored. The user-data is always set
** to a NULL pointer. The bNC parameter is not used.
**
+** MFUNCTION(zName, nArg, xPtr, xFunc)
+** For math-library functions. xPtr is an arbitrary pointer.
+**
** PURE_DATE(zName, nArg, iArg, bNC, xFunc)
** Used for "pure" date/time functions, this macro is like DFUNCTION
** except that it does set the SQLITE_FUNC_CONSTANT flags. iArg is
@@ -17248,6 +17516,9 @@ struct FuncDestructor {
#define SFUNCTION(zName, nArg, iArg, bNC, xFunc) \
{nArg, SQLITE_UTF8|SQLITE_DIRECTONLY|SQLITE_FUNC_UNSAFE, \
SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} }
+#define MFUNCTION(zName, nArg, xPtr, xFunc) \
+ {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \
+ xPtr, 0, xFunc, 0, 0, 0, #zName, {0} }
#define INLINE_FUNC(zName, nArg, iArg, mFlags) \
{nArg, SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \
SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} }
@@ -17342,7 +17613,12 @@ struct Column {
u16 colFlags; /* Boolean properties. See COLFLAG_ defines below */
};
-/* Allowed values for Column.colFlags:
+/* Allowed values for Column.colFlags.
+**
+** Constraints:
+** TF_HasVirtual == COLFLAG_VIRTUAL
+** TF_HasStored == COLFLAG_STORED
+** TF_HasHidden == COLFLAG_HIDDEN
*/
#define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */
#define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */
@@ -17418,9 +17694,7 @@ struct CollSeq {
** operator is NULL. It is added to certain comparison operators to
** prove that the operands are always NOT NULL.
*/
-#define SQLITE_KEEPNULL 0x08 /* Used by vector == or <> */
#define SQLITE_JUMPIFNULL 0x10 /* jumps if either operand is NULL */
-#define SQLITE_STOREP2 0x20 /* Store result in reg[P2] rather than jump */
#define SQLITE_NULLEQ 0x80 /* NULL=NULL */
#define SQLITE_NOTNULL 0x90 /* Assert that operands are never NULL */
@@ -17518,7 +17792,6 @@ struct Table {
#endif
Trigger *pTrigger; /* List of triggers stored in pSchema */
Schema *pSchema; /* Schema that contains this table */
- Table *pNextZombie; /* Next on the Parse.pZombieTab list */
};
/*
@@ -17532,11 +17805,12 @@ struct Table {
**
** Constraints:
**
-** TF_HasVirtual == COLFLAG_Virtual
-** TF_HasStored == COLFLAG_Stored
+** TF_HasVirtual == COLFLAG_VIRTUAL
+** TF_HasStored == COLFLAG_STORED
+** TF_HasHidden == COLFLAG_HIDDEN
*/
#define TF_Readonly 0x0001 /* Read-only system table */
-#define TF_Ephemeral 0x0002 /* An ephemeral table */
+#define TF_HasHidden 0x0002 /* Has one or more hidden columns */
#define TF_HasPrimaryKey 0x0004 /* Table has a primary key */
#define TF_Autoincrement 0x0008 /* Integer primary key is autoincrement */
#define TF_HasStat1 0x0010 /* nRowLogEst set from sqlite_stat1 */
@@ -17550,6 +17824,9 @@ struct Table {
#define TF_OOOHidden 0x0400 /* Out-of-Order hidden columns */
#define TF_HasNotNull 0x0800 /* Contains NOT NULL constraints */
#define TF_Shadow 0x1000 /* True for a shadow table */
+#define TF_HasStat4 0x2000 /* STAT4 info available for this table */
+#define TF_Ephemeral 0x4000 /* An ephemeral table */
+#define TF_Eponymous 0x8000 /* An eponymous virtual table */
/*
** Test to see whether or not a table is a virtual table. This is
@@ -17646,16 +17923,22 @@ struct FKey {
** is returned. REPLACE means that preexisting database rows that caused
** a UNIQUE constraint violation are removed so that the new insert or
** update can proceed. Processing continues and no error is reported.
+** UPDATE applies to insert operations only and means that the insert
+** is omitted and the DO UPDATE clause of an upsert is run instead.
**
-** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys.
+** RESTRICT, SETNULL, SETDFLT, and CASCADE actions apply only to foreign keys.
** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the
** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign
-** key is set to NULL. CASCADE means that a DELETE or UPDATE of the
+** key is set to NULL. SETDFLT means that the foreign key is set
+** to its default value. CASCADE means that a DELETE or UPDATE of the
** referenced table row is propagated into the row that holds the
** foreign key.
**
+** The OE_Default value is a place holder that means to use whatever
+** conflict resolution algorthm is required from context.
+**
** The following symbolic values are used to record which type
-** of action to take.
+** of conflict resolution action to take.
*/
#define OE_None 0 /* There is no constraint to check */
#define OE_Rollback 1 /* Fail the operation and rollback the transaction */
@@ -17909,10 +18192,10 @@ struct AggInfo {
FuncDef *pFunc; /* The aggregate function implementation */
int iMem; /* Memory location that acts as accumulator */
int iDistinct; /* Ephemeral table used to enforce DISTINCT */
+ int iDistAddr; /* Address of OP_OpenEphemeral */
} *aFunc;
int nFunc; /* Number of entries in aFunc[] */
u32 selId; /* Select to which this AggInfo belongs */
- AggInfo *pNext; /* Next in list of them all */
};
/*
@@ -18041,7 +18324,7 @@ struct Expr {
** TK_VARIABLE: variable number (always >= 1).
** TK_SELECT_COLUMN: column of the result vector */
i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */
- i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */
+ int iRightJoinTable; /* If EP_FromJoin, the right table of the join */
AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */
union {
Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL
@@ -18078,12 +18361,12 @@ struct Expr {
#define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */
#define EP_Win 0x008000 /* Contains window functions */
#define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */
- /* 0x020000 // available for reuse */
+#define EP_IfNullRow 0x020000 /* The TK_IF_NULL_ROW opcode */
#define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */
#define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */
#define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */
#define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */
-#define EP_Alias 0x400000 /* Is an alias for a result set column */
+ /* 0x400000 // Available */
#define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */
#define EP_WinFunc 0x1000000 /* TK_FUNCTION with Expr.y.pWin set */
#define EP_Subrtn 0x2000000 /* Uses Expr.y.sub. TK_IN, _SELECT, or _EXISTS */
@@ -18182,6 +18465,7 @@ struct Expr {
*/
struct ExprList {
int nExpr; /* Number of expressions on the list */
+ int nAlloc; /* Number of a[] slots allocated */
struct ExprList_item { /* For each expression in the list */
Expr *pExpr; /* The parse tree for this expression */
char *zEName; /* Token associated with this expression */
@@ -18232,6 +18516,46 @@ struct IdList {
};
/*
+** The SrcItem object represents a single term in the FROM clause of a query.
+** The SrcList object is mostly an array of SrcItems.
+*/
+struct SrcItem {
+ Schema *pSchema; /* Schema to which this item is fixed */
+ char *zDatabase; /* Name of database holding this table */
+ char *zName; /* Name of the table */
+ char *zAlias; /* The "B" part of a "A AS B" phrase. zName is the "A" */
+ Table *pTab; /* An SQL table corresponding to zName */
+ Select *pSelect; /* A SELECT statement used in place of a table name */
+ int addrFillSub; /* Address of subroutine to manifest a subquery */
+ int regReturn; /* Register holding return address of addrFillSub */
+ int regResult; /* Registers holding results of a co-routine */
+ struct {
+ u8 jointype; /* Type of join between this table and the previous */
+ unsigned notIndexed :1; /* True if there is a NOT INDEXED clause */
+ unsigned isIndexedBy :1; /* True if there is an INDEXED BY clause */
+ unsigned isTabFunc :1; /* True if table-valued-function syntax */
+ unsigned isCorrelated :1; /* True if sub-query is correlated */
+ unsigned viaCoroutine :1; /* Implemented as a co-routine */
+ unsigned isRecursive :1; /* True for recursive reference in WITH */
+ unsigned fromDDL :1; /* Comes from sqlite_schema */
+ unsigned isCte :1; /* This is a CTE */
+ unsigned notCte :1; /* This item may not match a CTE */
+ } fg;
+ int iCursor; /* The VDBE cursor number used to access this table */
+ Expr *pOn; /* The ON clause of a join */
+ IdList *pUsing; /* The USING clause of a join */
+ Bitmask colUsed; /* Bit N (1<<N) set if column N of pTab is used */
+ union {
+ char *zIndexedBy; /* Identifier from "INDEXED BY <zIndex>" clause */
+ ExprList *pFuncArg; /* Arguments to table-valued-function */
+ } u1;
+ union {
+ Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */
+ CteUse *pCteUse; /* CTE Usage info info fg.isCte is true */
+ } u2;
+};
+
+/*
** The following structure describes the FROM clause of a SELECT statement.
** Each table or subquery in the FROM clause is a separate element of
** the SrcList.a[] array.
@@ -18253,36 +18577,7 @@ struct IdList {
struct SrcList {
int nSrc; /* Number of tables or subqueries in the FROM clause */
u32 nAlloc; /* Number of entries allocated in a[] below */
- struct SrcList_item {
- Schema *pSchema; /* Schema to which this item is fixed */
- char *zDatabase; /* Name of database holding this table */
- char *zName; /* Name of the table */
- char *zAlias; /* The "B" part of a "A AS B" phrase. zName is the "A" */
- Table *pTab; /* An SQL table corresponding to zName */
- Select *pSelect; /* A SELECT statement used in place of a table name */
- int addrFillSub; /* Address of subroutine to manifest a subquery */
- int regReturn; /* Register holding return address of addrFillSub */
- int regResult; /* Registers holding results of a co-routine */
- struct {
- u8 jointype; /* Type of join between this table and the previous */
- unsigned notIndexed :1; /* True if there is a NOT INDEXED clause */
- unsigned isIndexedBy :1; /* True if there is an INDEXED BY clause */
- unsigned isTabFunc :1; /* True if table-valued-function syntax */
- unsigned isCorrelated :1; /* True if sub-query is correlated */
- unsigned viaCoroutine :1; /* Implemented as a co-routine */
- unsigned isRecursive :1; /* True for recursive reference in WITH */
- unsigned fromDDL :1; /* Comes from sqlite_schema */
- } fg;
- int iCursor; /* The VDBE cursor number used to access this table */
- Expr *pOn; /* The ON clause of a join */
- IdList *pUsing; /* The USING clause of a join */
- Bitmask colUsed; /* Bit N (1<<N) set if column N of pTab is used */
- union {
- char *zIndexedBy; /* Identifier from "INDEXED BY <zIndex>" clause */
- ExprList *pFuncArg; /* Arguments to table-valued-function */
- } u1;
- Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */
- } a[1]; /* One entry for each identifier on the list */
+ SrcItem a[1]; /* One entry for each identifier on the list */
};
/*
@@ -18316,9 +18611,9 @@ struct SrcList {
#define WHERE_DISTINCTBY 0x0080 /* pOrderby is really a DISTINCT clause */
#define WHERE_WANT_DISTINCT 0x0100 /* All output needs to be distinct */
#define WHERE_SORTBYGROUP 0x0200 /* Support sqlite3WhereIsSorted() */
-#define WHERE_SEEK_TABLE 0x0400 /* Do not defer seeks on main table */
+#define WHERE_AGG_DISTINCT 0x0400 /* Query is "SELECT agg(DISTINCT ...)" */
#define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */
-#define WHERE_SEEK_UNIQ_TABLE 0x1000 /* Do not defer seeks if unique */
+ /* 0x1000 not currently used */
/* 0x2000 not currently used */
#define WHERE_USE_LIMIT 0x4000 /* Use the LIMIT in cost estimates */
/* 0x8000 not currently used */
@@ -18358,10 +18653,11 @@ struct NameContext {
ExprList *pEList; /* Optional list of result-set columns */
AggInfo *pAggInfo; /* Information about aggregates at this level */
Upsert *pUpsert; /* ON CONFLICT clause information from an upsert */
+ int iBaseReg; /* For TK_REGISTER when parsing RETURNING */
} uNC;
NameContext *pNext; /* Next outer name context. NULL for outermost */
int nRef; /* Number of names resolved by this context */
- int nErr; /* Number of errors encountered while resolving names */
+ int nNcErr; /* Number of errors encountered while resolving names */
int ncFlags; /* Zero or more NC_* flags defined below */
Select *pWinSelect; /* SELECT statement for any window functions */
};
@@ -18386,6 +18682,7 @@ struct NameContext {
#define NC_UEList 0x00080 /* True if uNC.pEList is used */
#define NC_UAggInfo 0x00100 /* True if uNC.pAggInfo is used */
#define NC_UUpsert 0x00200 /* True if uNC.pUpsert is used */
+#define NC_UBaseReg 0x00400 /* True if uNC.iBaseReg is used */
#define NC_MinMaxAgg 0x01000 /* min/max aggregates seen. See note above */
#define NC_Complex 0x02000 /* True if a function or subquery seen */
#define NC_AllowWin 0x04000 /* Window functions are allowed here */
@@ -18393,6 +18690,7 @@ struct NameContext {
#define NC_IsDDL 0x10000 /* Resolving names in a CREATE statement */
#define NC_InAggFunc 0x20000 /* True if analyzing arguments to an agg func */
#define NC_FromDDL 0x40000 /* SQL text comes from sqlite_schema */
+#define NC_NoSelect 0x80000 /* Do not descend into sub-selects */
/*
** An instance of the following object describes a single ON CONFLICT
@@ -18409,15 +18707,21 @@ struct NameContext {
** WHERE clause is omitted.
*/
struct Upsert {
- ExprList *pUpsertTarget; /* Optional description of conflicting index */
+ ExprList *pUpsertTarget; /* Optional description of conflict target */
Expr *pUpsertTargetWhere; /* WHERE clause for partial index targets */
ExprList *pUpsertSet; /* The SET clause from an ON CONFLICT UPDATE */
Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */
- /* The fields above comprise the parse tree for the upsert clause.
- ** The fields below are used to transfer information from the INSERT
- ** processing down into the UPDATE processing while generating code.
- ** Upsert owns the memory allocated above, but not the memory below. */
- Index *pUpsertIdx; /* Constraint that pUpsertTarget identifies */
+ Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */
+ u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */
+ /* Above this point is the parse tree for the ON CONFLICT clauses.
+ ** The next group of fields stores intermediate data. */
+ void *pToFree; /* Free memory when deleting the Upsert object */
+ /* All fields above are owned by the Upsert object and must be freed
+ ** when the Upsert is destroyed. The fields below are used to transfer
+ ** information from the INSERT processing down into the UPDATE processing
+ ** while generating code. The fields below are owned by the INSERT
+ ** statement and will be freed by INSERT processing. */
+ Index *pUpsertIdx; /* UNIQUE constraint specified by pUpsertTarget */
SrcList *pUpsertSrc; /* Table to be updated */
int regData; /* First register holding array of VALUES */
int iDataCur; /* Index of the data cursor */
@@ -18497,6 +18801,9 @@ struct Select {
#define SF_View 0x0200000 /* SELECT statement is a view */
#define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */
#define SF_UpdateFrom 0x0800000 /* Statement is an UPDATE...FROM */
+#define SF_PushDown 0x1000000 /* SELECT has be modified by push-down opt */
+#define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */
+#define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */
/*
** The results of a SELECT can be distributed in several ways, as defined
@@ -18515,9 +18822,6 @@ struct Select {
** statements within triggers whose only purpose is
** the side-effects of functions.
**
-** All of the above are free to ignore their ORDER BY clause. Those that
-** follow must honor the ORDER BY clause.
-**
** SRT_Output Generate a row of output (using the OP_ResultRow
** opcode) for each row in the result set.
**
@@ -18574,13 +18878,18 @@ struct Select {
#define SRT_Except 2 /* Remove result from a UNION index */
#define SRT_Exists 3 /* Store 1 if the result is not empty */
#define SRT_Discard 4 /* Do not save the results anywhere */
-#define SRT_Fifo 5 /* Store result as data with an automatic rowid */
-#define SRT_DistFifo 6 /* Like SRT_Fifo, but unique results only */
+#define SRT_DistFifo 5 /* Like SRT_Fifo, but unique results only */
+#define SRT_DistQueue 6 /* Like SRT_Queue, but unique results only */
+
+/* The DISTINCT clause is ignored for all of the above. Not that
+** IgnorableDistinct() implies IgnorableOrderby() */
+#define IgnorableDistinct(X) ((X->eDest)<=SRT_DistQueue)
+
#define SRT_Queue 7 /* Store result in an queue */
-#define SRT_DistQueue 8 /* Like SRT_Queue, but unique results only */
+#define SRT_Fifo 8 /* Store result as data with an automatic rowid */
/* The ORDER BY clause is ignored for all of the above */
-#define IgnorableOrderby(X) ((X->eDest)<=SRT_DistQueue)
+#define IgnorableOrderby(X) ((X->eDest)<=SRT_Fifo)
#define SRT_Output 9 /* Output each row of result */
#define SRT_Mem 10 /* Store result in a memory cell */
@@ -18666,6 +18975,17 @@ struct TriggerPrg {
#endif
/*
+** An instance of the ParseCleanup object specifies an operation that
+** should be performed after parsing to deallocation resources obtained
+** during the parse and which are no longer needed.
+*/
+struct ParseCleanup {
+ ParseCleanup *pNext; /* Next cleanup task */
+ void *pPtr; /* Pointer to object to deallocate */
+ void (*xCleanup)(sqlite3*,void*); /* Deallocation routine */
+};
+
+/*
** An SQL parser context. A copy of this structure is passed through
** the parser and down into all the parser action routine in order to
** carry around information that is global to the entire parse.
@@ -18696,6 +19016,9 @@ struct Parse {
u8 okConstFactor; /* OK to factor out constants */
u8 disableLookaside; /* Number of times lookaside has been disabled */
u8 disableVtab; /* Disable all virtual tables for this parse */
+#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
+ u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */
+#endif
int nRangeReg; /* Size of the temporary register block */
int iRangeReg; /* First register in temporary register block */
int nErr; /* Number of errors seen */
@@ -18723,12 +19046,15 @@ struct Parse {
Parse *pToplevel; /* Parse structure for main program (or NULL) */
Table *pTriggerTab; /* Table triggers are being coded for */
Parse *pParentParse; /* Parent parser if this parser is nested */
- AggInfo *pAggList; /* List of all AggInfo objects */
- int addrCrTab; /* Address of OP_CreateBtree opcode on CREATE TABLE */
+ union {
+ int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */
+ Returning *pReturning; /* The RETURNING clause */
+ } u1;
u32 nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */
u32 oldmask; /* Mask of old.* columns referenced */
u32 newmask; /* Mask of new.* columns referenced */
u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */
+ u8 bReturning; /* Coding a RETURNING trigger */
u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */
u8 disableTriggers; /* True to disable triggers */
@@ -18774,10 +19100,9 @@ struct Parse {
Token sArg; /* Complete text of a module argument */
Table **apVtabLock; /* Pointer to virtual tables needing locking */
#endif
- Table *pZombieTab; /* List of Table objects to delete after code gen */
TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */
With *pWith; /* Current WITH clause, or NULL */
- With *pWithToFree; /* Free this WITH object at the end of the parse */
+ ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */
#ifndef SQLITE_OMIT_ALTERTABLE
RenameToken *pRename; /* Tokens subject to renaming by ALTER TABLE */
#endif
@@ -18857,6 +19182,7 @@ struct AuthContext {
#define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete/Insert: save cursor pos */
#define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */
#define OPFLAG_NOCHNG_MAGIC 0x6d /* OP_MakeRecord: serialtype 10 is ok */
+#define OPFLAG_PREFORMAT 0x80 /* OP_Insert uses preformatted cell */
/*
* Each trigger present in the database schema is stored as an instance of
@@ -18878,6 +19204,7 @@ struct Trigger {
char *table; /* The table or view to which the trigger applies */
u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT */
u8 tr_tm; /* One of TRIGGER_BEFORE, TRIGGER_AFTER */
+ u8 bReturning; /* This trigger implements a RETURNING clause */
Expr *pWhen; /* The WHEN clause of the expression (may be NULL) */
IdList *pColumns; /* If this is an UPDATE OF <column-list> trigger,
the <column-list> is stored here */
@@ -18936,14 +19263,15 @@ struct Trigger {
*
*/
struct TriggerStep {
- u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */
+ u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT,
+ ** or TK_RETURNING */
u8 orconf; /* OE_Rollback etc. */
Trigger *pTrig; /* The trigger that this step is a part of */
Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */
char *zTarget; /* Target table for DELETE, UPDATE, INSERT */
SrcList *pFrom; /* FROM clause for UPDATE statement (if any) */
Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */
- ExprList *pExprList; /* SET clause for UPDATE */
+ ExprList *pExprList; /* SET clause for UPDATE, or RETURNING clause */
IdList *pIdList; /* Column names for INSERT */
Upsert *pUpsert; /* Upsert clauses on an INSERT */
char *zSpan; /* Original SQL text of this command */
@@ -18952,18 +19280,16 @@ struct TriggerStep {
};
/*
-** The following structure contains information used by the sqliteFix...
-** routines as they walk the parse tree to make database references
-** explicit.
+** Information about a RETURNING clause
*/
-typedef struct DbFixer DbFixer;
-struct DbFixer {
- Parse *pParse; /* The parsing context. Error messages written here */
- Schema *pSchema; /* Fix items to this schema */
- u8 bTemp; /* True for TEMP schema entries */
- const char *zDb; /* Make sure all objects are contained in this database */
- const char *zType; /* Type of the container - used for error messages */
- const Token *pName; /* Name of the container - used for error messages */
+struct Returning {
+ Parse *pParse; /* The parse that includes the RETURNING clause */
+ ExprList *pReturnEL; /* List of expressions to return */
+ Trigger retTrig; /* The transient trigger that implements RETURNING */
+ TriggerStep retTStep; /* The trigger step */
+ int iRetCur; /* Transient table holding RETURNING results */
+ int nRetCol; /* Number of in pReturnEL after expansion */
+ int iRetReg; /* Register array for holding a row of RETURNING */
};
/*
@@ -19003,7 +19329,24 @@ typedef struct {
/*
** Allowed values for mInitFlags
*/
-#define INITFLAG_AlterTable 0x0001 /* This is a reparse after ALTER TABLE */
+#define INITFLAG_AlterRename 0x0001 /* Reparse after a RENAME */
+#define INITFLAG_AlterDrop 0x0002 /* Reparse after a DROP COLUMN */
+
+/* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled
+** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning
+** parameters are for temporary use during development, to help find
+** optimial values for parameters in the query planner. The should not
+** be used on trunk check-ins. They are a temporary mechanism available
+** for transient development builds only.
+**
+** Tuning parameters are numbered starting with 1.
+*/
+#define SQLITE_NTUNE 6 /* Should be zero for all trunk check-ins */
+#ifdef SQLITE_DEBUG
+# define Tuning(X) (sqlite3Config.aTune[(X)-1])
+#else
+# define Tuning(X) 0
+#endif
/*
** Structure containing global configuration data for the SQLite library.
@@ -19059,7 +19402,7 @@ struct Sqlite3Config {
void (*xVdbeBranch)(void*,unsigned iSrcLine,u8 eThis,u8 eMx); /* Callback */
void *pVdbeBranchArg; /* 1st argument */
#endif
-#ifdef SQLITE_ENABLE_DESERIALIZE
+#ifndef SQLITE_OMIT_DESERIALIZE
sqlite3_int64 mxMemdbSize; /* Default max memdb size */
#endif
#ifndef SQLITE_UNTESTABLE
@@ -19069,6 +19412,10 @@ struct Sqlite3Config {
int iOnceResetThreshold; /* When to reset OP_Once counters */
u32 szSorterRef; /* Min size in bytes to use sorter-refs */
unsigned int iPrngSeed; /* Alternative fixed seed for the PRNG */
+ /* vvvv--- must be last ---vvv */
+#ifdef SQLITE_DEBUG
+ sqlite3_int64 aTune[SQLITE_NTUNE]; /* Tuning parameters */
+#endif
};
/*
@@ -19115,10 +19462,26 @@ struct Walker {
struct WhereConst *pConst; /* WHERE clause constants */
struct RenameCtx *pRename; /* RENAME COLUMN context */
struct Table *pTab; /* Table of generated column */
- struct SrcList_item *pSrcItem; /* A single FROM clause item */
+ SrcItem *pSrcItem; /* A single FROM clause item */
+ DbFixer *pFix;
} u;
};
+/*
+** The following structure contains information used by the sqliteFix...
+** routines as they walk the parse tree to make database references
+** explicit.
+*/
+struct DbFixer {
+ Parse *pParse; /* The parsing context. Error messages written here */
+ Walker w; /* Walker object */
+ Schema *pSchema; /* Fix items to this schema */
+ u8 bTemp; /* True for TEMP schema entries */
+ const char *zDb; /* Make sure all objects are contained in this database */
+ const char *zType; /* Type of the container - used for error messages */
+ const Token *pName; /* Name of the container - used for error messages */
+};
+
/* Forward declarations */
SQLITE_PRIVATE int sqlite3WalkExpr(Walker*, Expr*);
SQLITE_PRIVATE int sqlite3WalkExprList(Walker*, ExprList*);
@@ -19130,11 +19493,18 @@ SQLITE_PRIVATE int sqlite3SelectWalkNoop(Walker*, Select*);
SQLITE_PRIVATE int sqlite3SelectWalkFail(Walker*, Select*);
SQLITE_PRIVATE int sqlite3WalkerDepthIncrease(Walker*,Select*);
SQLITE_PRIVATE void sqlite3WalkerDepthDecrease(Walker*,Select*);
+SQLITE_PRIVATE void sqlite3WalkWinDefnDummyCallback(Walker*,Select*);
#ifdef SQLITE_DEBUG
SQLITE_PRIVATE void sqlite3SelectWalkAssert2(Walker*, Select*);
#endif
+#ifndef SQLITE_OMIT_CTE
+SQLITE_PRIVATE void sqlite3SelectPopWith(Walker*, Select*);
+#else
+# define sqlite3SelectPopWith 0
+#endif
+
/*
** Return code from the parse-tree walking primitives and their
** callbacks.
@@ -19144,20 +19514,56 @@ SQLITE_PRIVATE void sqlite3SelectWalkAssert2(Walker*, Select*);
#define WRC_Abort 2 /* Abandon the tree walk */
/*
-** An instance of this structure represents a set of one or more CTEs
-** (common table expressions) created by a single WITH clause.
+** A single common table expression
+*/
+struct Cte {
+ char *zName; /* Name of this CTE */
+ ExprList *pCols; /* List of explicit column names, or NULL */
+ Select *pSelect; /* The definition of this CTE */
+ const char *zCteErr; /* Error message for circular references */
+ CteUse *pUse; /* Usage information for this CTE */
+ u8 eM10d; /* The MATERIALIZED flag */
+};
+
+/*
+** Allowed values for the materialized flag (eM10d):
+*/
+#define M10d_Yes 0 /* AS MATERIALIZED */
+#define M10d_Any 1 /* Not specified. Query planner's choice */
+#define M10d_No 2 /* AS NOT MATERIALIZED */
+
+/*
+** An instance of the With object represents a WITH clause containing
+** one or more CTEs (common table expressions).
*/
struct With {
- int nCte; /* Number of CTEs in the WITH clause */
- With *pOuter; /* Containing WITH clause, or NULL */
- struct Cte { /* For each CTE in the WITH clause.... */
- char *zName; /* Name of this CTE */
- ExprList *pCols; /* List of explicit column names, or NULL */
- Select *pSelect; /* The definition of this CTE */
- const char *zCteErr; /* Error message for circular references */
- } a[1];
+ int nCte; /* Number of CTEs in the WITH clause */
+ int bView; /* Belongs to the outermost Select of a view */
+ With *pOuter; /* Containing WITH clause, or NULL */
+ Cte a[1]; /* For each CTE in the WITH clause.... */
+};
+
+/*
+** The Cte object is not guaranteed to persist for the entire duration
+** of code generation. (The query flattener or other parser tree
+** edits might delete it.) The following object records information
+** about each Common Table Expression that must be preserved for the
+** duration of the parse.
+**
+** The CteUse objects are freed using sqlite3ParserAddCleanup() rather
+** than sqlite3SelectDelete(), which is what enables them to persist
+** until the end of code generation.
+*/
+struct CteUse {
+ int nUse; /* Number of users of this CTE */
+ int addrM9e; /* Start of subroutine to compute materialization */
+ int regRtn; /* Return address register for addrM9e subroutine */
+ int iCur; /* Ephemeral table holding the materialization */
+ LogEst nRowEst; /* Estimated number of rows in the table */
+ u8 eM10d; /* The MATERIALIZED flag */
};
+
#ifdef SQLITE_DEBUG
/*
** An instance of the TreeView object is used for printing the content of
@@ -19235,7 +19641,6 @@ SQLITE_PRIVATE int sqlite3WindowCompare(Parse*, Window*, Window*, int);
SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse*, Select*);
SQLITE_PRIVATE void sqlite3WindowCodeStep(Parse*, Select*, WhereInfo*, int, int);
SQLITE_PRIVATE int sqlite3WindowRewrite(Parse*, Select*);
-SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse*, struct SrcList_item*);
SQLITE_PRIVATE void sqlite3WindowUpdate(Parse*, Window*, Window*, FuncDef*);
SQLITE_PRIVATE Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p);
SQLITE_PRIVATE Window *sqlite3WindowListDup(sqlite3 *db, Window *p);
@@ -19504,6 +19909,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*, int);
SQLITE_PRIVATE void sqlite3ExprFunctionUsable(Parse*,Expr*,FuncDef*);
SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32);
SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3*, Expr*);
+SQLITE_PRIVATE void sqlite3ExprDeferredDelete(Parse*, Expr*);
SQLITE_PRIVATE void sqlite3ExprUnmapAndDelete(Parse*, Expr*);
SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*);
SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*);
@@ -19525,6 +19931,7 @@ SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3*,int);
SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3*);
SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3*);
SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3*,Table*);
+SQLITE_PRIVATE void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect);
SQLITE_PRIVATE int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**);
SQLITE_PRIVATE void sqlite3SelectAddColumnTypeAndCollation(Parse*,Table*,Select*,char);
SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse*,Select*,char);
@@ -19547,11 +19954,12 @@ SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table*, Column*);
SQLITE_PRIVATE void sqlite3AddColumn(Parse*,Token*,Token*);
SQLITE_PRIVATE void sqlite3AddNotNull(Parse*, int);
SQLITE_PRIVATE void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int);
-SQLITE_PRIVATE void sqlite3AddCheckConstraint(Parse*, Expr*);
+SQLITE_PRIVATE void sqlite3AddCheckConstraint(Parse*, Expr*, const char*, const char*);
SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse*,Expr*,const char*,const char*);
SQLITE_PRIVATE void sqlite3AddCollateType(Parse*, Token*);
SQLITE_PRIVATE void sqlite3AddGenerated(Parse*,Expr*,Token*);
SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*);
+SQLITE_PRIVATE void sqlite3AddReturning(Parse*,ExprList*);
SQLITE_PRIVATE int sqlite3ParseUri(const char*,const char*,unsigned int*,
sqlite3_vfs**,char**,char **);
#define sqlite3CodecQueryParameters(A,B,C) 0
@@ -19617,7 +20025,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, T
Token*, Select*, Expr*, IdList*);
SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *, SrcList *, Token *);
SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse*, SrcList*, ExprList*);
-SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *, struct SrcList_item *);
+SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *, SrcItem *);
SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList*);
SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse*, SrcList*);
SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3*, IdList*);
@@ -19645,6 +20053,7 @@ SQLITE_PRIVATE LogEst sqlite3WhereOutputRowCount(WhereInfo*);
SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo*);
SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo*);
SQLITE_PRIVATE int sqlite3WhereOrderByLimitOptLabel(WhereInfo*);
+SQLITE_PRIVATE void sqlite3WhereMinMaxOptEarlyOut(Vdbe*,WhereInfo*);
SQLITE_PRIVATE int sqlite3WhereIsSorted(WhereInfo*);
SQLITE_PRIVATE int sqlite3WhereContinueLabel(WhereInfo*);
SQLITE_PRIVATE int sqlite3WhereBreakLabel(WhereInfo*);
@@ -19678,7 +20087,7 @@ SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3*,const char*, const char*);
#define LOCATE_VIEW 0x01
#define LOCATE_NOERR 0x02
SQLITE_PRIVATE Table *sqlite3LocateTable(Parse*,u32 flags,const char*, const char*);
-SQLITE_PRIVATE Table *sqlite3LocateTableItem(Parse*,u32 flags,struct SrcList_item *);
+SQLITE_PRIVATE Table *sqlite3LocateTableItem(Parse*,u32 flags,SrcItem *);
SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3*,const char*, const char*);
SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*);
SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*);
@@ -19758,6 +20167,7 @@ SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*);
SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*);
SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*);
SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int);
+SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p);
#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int);
@@ -19806,6 +20216,7 @@ SQLITE_PRIVATE SrcList *sqlite3TriggerStepSrc(Parse*, TriggerStep*);
#endif
SQLITE_PRIVATE int sqlite3JoinType(Parse*, Token*, Token*, Token*);
+SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol);
SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr*,int);
SQLITE_PRIVATE void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int);
SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse*, int);
@@ -19828,7 +20239,6 @@ SQLITE_PRIVATE void sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Tok
SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*);
SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*);
SQLITE_PRIVATE int sqlite3FixExpr(DbFixer*, Expr*);
-SQLITE_PRIVATE int sqlite3FixExprList(DbFixer*, ExprList*);
SQLITE_PRIVATE int sqlite3FixTriggerStep(DbFixer*, TriggerStep*);
SQLITE_PRIVATE int sqlite3RealSameAsInt(double,sqlite3_int64);
SQLITE_PRIVATE void sqlite3Int64ToText(i64,char*);
@@ -19891,6 +20301,7 @@ SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8);
SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*);
SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...);
SQLITE_PRIVATE void sqlite3Error(sqlite3*,int);
+SQLITE_PRIVATE void sqlite3ErrorClear(sqlite3*);
SQLITE_PRIVATE void sqlite3SystemError(sqlite3*,int);
SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3*, const char *z, int n);
SQLITE_PRIVATE u8 sqlite3HexToInt(int h);
@@ -19900,7 +20311,7 @@ SQLITE_PRIVATE int sqlite3TwoPartName(Parse *, Token *, Token *, Token **);
SQLITE_PRIVATE const char *sqlite3ErrName(int);
#endif
-#ifdef SQLITE_ENABLE_DESERIALIZE
+#ifndef SQLITE_OMIT_DESERIALIZE
SQLITE_PRIVATE int sqlite3MemdbInit(void);
#endif
@@ -19951,10 +20362,12 @@ SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8);
SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[];
SQLITE_PRIVATE const char sqlite3StrBINARY[];
SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[];
+SQLITE_PRIVATE const unsigned char *sqlite3aLTb;
+SQLITE_PRIVATE const unsigned char *sqlite3aEQb;
+SQLITE_PRIVATE const unsigned char *sqlite3aGTb;
SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[];
SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config;
SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions;
-SQLITE_API extern u32 sqlite3_unsupported_selecttrace;
#ifndef SQLITE_OMIT_WSD
SQLITE_PRIVATE int sqlite3PendingByte;
#endif
@@ -19973,6 +20386,7 @@ SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*, int);
SQLITE_PRIVATE void sqlite3CodeRhsOfIN(Parse*, Expr*, int);
SQLITE_PRIVATE int sqlite3CodeSubselect(Parse*, Expr*);
SQLITE_PRIVATE void sqlite3SelectPrep(Parse*, Select*, NameContext*);
+SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse*, SrcItem*);
SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p);
SQLITE_PRIVATE int sqlite3MatchEName(
const struct ExprList_item*,
@@ -19990,6 +20404,7 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const
SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *, Table *, int, int);
SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *, Token *);
SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
+SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse*, SrcList*, Token*);
SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse*, void*, Token*);
SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse*, void *pTo, void *pFrom);
SQLITE_PRIVATE void sqlite3RenameExprUnmap(Parse*, Expr*);
@@ -20013,6 +20428,7 @@ SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo*);
SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoRef(KeyInfo*);
SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse*, Index*);
SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoFromExprList(Parse*, ExprList*, int, int);
+SQLITE_PRIVATE const char *sqlite3SelectOpName(int);
SQLITE_PRIVATE int sqlite3HasExplicitNulls(Parse*, ExprList*);
#ifdef SQLITE_DEBUG
@@ -20143,6 +20559,7 @@ SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*);
SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe*, const char*, int);
SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *);
SQLITE_PRIVATE void sqlite3ParserReset(Parse*);
+SQLITE_PRIVATE void *sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*);
#ifdef SQLITE_ENABLE_NORMALIZE
SQLITE_PRIVATE char *sqlite3Normalize(Vdbe*, const char*);
#endif
@@ -20157,23 +20574,32 @@ SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*);
SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int);
#endif
#ifndef SQLITE_OMIT_CTE
-SQLITE_PRIVATE With *sqlite3WithAdd(Parse*,With*,Token*,ExprList*,Select*);
+SQLITE_PRIVATE Cte *sqlite3CteNew(Parse*,Token*,ExprList*,Select*,u8);
+SQLITE_PRIVATE void sqlite3CteDelete(sqlite3*,Cte*);
+SQLITE_PRIVATE With *sqlite3WithAdd(Parse*,With*,Cte*);
SQLITE_PRIVATE void sqlite3WithDelete(sqlite3*,With*);
-SQLITE_PRIVATE void sqlite3WithPush(Parse*, With*, u8);
+SQLITE_PRIVATE With *sqlite3WithPush(Parse*, With*, u8);
#else
-#define sqlite3WithPush(x,y,z)
-#define sqlite3WithDelete(x,y)
+# define sqlite3CteNew(P,T,E,S) ((void*)0)
+# define sqlite3CteDelete(D,C)
+# define sqlite3CteWithAdd(P,W,C) ((void*)0)
+# define sqlite3WithDelete(x,y)
+# define sqlite3WithPush(x,y,z)
#endif
#ifndef SQLITE_OMIT_UPSERT
-SQLITE_PRIVATE Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*);
+SQLITE_PRIVATE Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*);
SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3*,Upsert*);
SQLITE_PRIVATE Upsert *sqlite3UpsertDup(sqlite3*,Upsert*);
SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*);
SQLITE_PRIVATE void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int);
+SQLITE_PRIVATE Upsert *sqlite3UpsertOfIndex(Upsert*,Index*);
+SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert*);
#else
-#define sqlite3UpsertNew(v,w,x,y,z) ((Upsert*)0)
+#define sqlite3UpsertNew(u,v,w,x,y,z) ((Upsert*)0)
#define sqlite3UpsertDelete(x,y)
-#define sqlite3UpsertDup(x,y) ((Upsert*)0)
+#define sqlite3UpsertDup(x,y) ((Upsert*)0)
+#define sqlite3UpsertOfIndex(x,y) ((Upsert*)0)
+#define sqlite3UpsertNextIsIPK(x) 0
#endif
@@ -20405,7 +20831,7 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = {
198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,
216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,
234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,
- 252,253,254,255
+ 252,253,254,255,
#endif
#ifdef SQLITE_EBCDIC
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 0x */
@@ -20425,7 +20851,35 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = {
224,225,162,163,164,165,166,167,168,169,234,235,236,237,238,239, /* Ex */
240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, /* Fx */
#endif
+/* All of the upper-to-lower conversion data is above. The following
+** 18 integers are completely unrelated. They are appended to the
+** sqlite3UpperToLower[] array to avoid UBSAN warnings. Here's what is
+** going on:
+**
+** The SQL comparison operators (<>, =, >, <=, <, and >=) are implemented
+** by invoking sqlite3MemCompare(A,B) which compares values A and B and
+** returns negative, zero, or positive if A is less then, equal to, or
+** greater than B, respectively. Then the true false results is found by
+** consulting sqlite3aLTb[opcode], sqlite3aEQb[opcode], or
+** sqlite3aGTb[opcode] depending on whether the result of compare(A,B)
+** is negative, zero, or positive, where opcode is the specific opcode.
+** The only works because the comparison opcodes are consecutive and in
+** this order: NE EQ GT LE LT GE. Various assert()s throughout the code
+** ensure that is the case.
+**
+** These elements must be appended to another array. Otherwise the
+** index (here shown as [256-OP_Ne]) would be out-of-bounds and thus
+** be undefined behavior. That's goofy, but the C-standards people thought
+** it was a good idea, so here we are.
+*/
+/* NE EQ GT LE LT GE */
+ 1, 0, 0, 1, 1, 0, /* aLTb[]: Use when compare(A,B) less than zero */
+ 0, 1, 0, 1, 0, 1, /* aEQb[]: Use when compare(A,B) equals zero */
+ 1, 0, 1, 0, 0, 1 /* aGTb[]: Use when compare(A,B) greater than zero*/
};
+SQLITE_PRIVATE const unsigned char *sqlite3aLTb = &sqlite3UpperToLower[256-OP_Ne];
+SQLITE_PRIVATE const unsigned char *sqlite3aEQb = &sqlite3UpperToLower[256+6-OP_Ne];
+SQLITE_PRIVATE const unsigned char *sqlite3aGTb = &sqlite3UpperToLower[256+12-OP_Ne];
/*
** The following 256 byte lookup table is used to support SQLites built-in
@@ -20619,7 +21073,7 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = {
0, /* xVdbeBranch */
0, /* pVbeBranchArg */
#endif
-#ifdef SQLITE_ENABLE_DESERIALIZE
+#ifndef SQLITE_OMIT_DESERIALIZE
SQLITE_MEMDB_DEFAULT_MAXSIZE, /* mxMemdbSize */
#endif
#ifndef SQLITE_UNTESTABLE
@@ -20669,9 +21123,10 @@ SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000;
#endif
/*
-** Flags for select tracing and the ".selecttrace" macro of the CLI
+** Tracing flags set by SQLITE_TESTCTRL_TRACEFLAGS.
*/
-SQLITE_API u32 sqlite3_unsupported_selecttrace = 0;
+SQLITE_PRIVATE u32 sqlite3SelectTrace = 0;
+SQLITE_PRIVATE u32 sqlite3WhereTrace = 0;
/* #include "opcodes.h" */
/*
@@ -20795,7 +21250,8 @@ struct VdbeCursor {
Bool isEphemeral:1; /* True for an ephemeral table */
Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */
Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */
- Bool seekHit:1; /* See the OP_SeekHit and OP_IfNoHope opcodes */
+ Bool hasBeenDuped:1; /* This cursor was source or target of OP_OpenDup */
+ u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */
Btree *pBtx; /* Separate file holding temporary table */
i64 seqCount; /* Sequence counter */
u32 *aAltMap; /* Mapping from table to index column numbers */
@@ -21090,7 +21546,7 @@ struct Vdbe {
Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */
Parse *pParse; /* Parsing context used to create this Vdbe */
ynVar nVar; /* Number of entries in aVar[] */
- u32 magic; /* Magic number for sanity checking */
+ u32 iVdbeMagic; /* Magic number defining state of the SQL statement */
int nMem; /* Number of memory locations currently allocated */
int nCursor; /* Number of slots in apCsr[] */
u32 cacheCtr; /* VdbeCursor row cache generation counter */
@@ -21180,6 +21636,7 @@ struct PreUpdate {
UnpackedRecord *pUnpacked; /* Unpacked version of aRecord[] */
UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */
int iNewReg; /* Register for new.* values */
+ int iBlobWrite; /* Value returned by preupdate_blobwrite() */
i64 iKey1; /* First key value passed to hook */
i64 iKey2; /* Second key value passed to hook */
Mem *aNew; /* Array of new.* values */
@@ -21223,7 +21680,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem*, const Mem*);
SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int);
SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem*, Mem*);
SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem*);
-SQLITE_PRIVATE int sqlite3VdbeMemSetStr(Mem*, const char*, int, u8, void(*)(void*));
+SQLITE_PRIVATE int sqlite3VdbeMemSetStr(Mem*, const char*, i64, u8, void(*)(void*));
SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem*, i64);
#ifdef SQLITE_OMIT_FLOATING_POINT
# define sqlite3VdbeMemSetDouble sqlite3VdbeMemSetInt64
@@ -21268,7 +21725,8 @@ SQLITE_PRIVATE void sqlite3VdbeFrameMemDel(void*); /* Destructor on Mem */
SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame*); /* Actually deletes the Frame */
SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *);
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int);
+SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
+ Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int,int);
#endif
SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p);
@@ -22595,6 +23053,7 @@ static int isDate(
int eType;
memset(p, 0, sizeof(*p));
if( argc==0 ){
+ if( !sqlite3NotPureFunc(context) ) return 1;
return setDateTimeToCurrent(context, p);
}
if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT
@@ -23095,6 +23554,8 @@ SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){
#ifdef SQLITE_TEST
if( op!=SQLITE_FCNTL_COMMIT_PHASETWO
&& op!=SQLITE_FCNTL_LOCK_TIMEOUT
+ && op!=SQLITE_FCNTL_CKPT_DONE
+ && op!=SQLITE_FCNTL_CKPT_START
){
/* Faults are not injected into COMMIT_PHASETWO because, assuming SQLite
** is using a regular VFS, it is called after the corresponding
@@ -23105,7 +23566,12 @@ SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){
** The core must call OsFileControl() though, not OsFileControlHint(),
** as if a custom VFS (e.g. zipvfs) returns an error here, it probably
** means the commit really has failed and an error should be returned
- ** to the user. */
+ ** to the user.
+ **
+ ** The CKPT_DONE and CKPT_START file-controls are write-only signals
+ ** to the cksumvfs. Their return code is meaningless and is ignored
+ ** by the SQLite core, so there is no point in simulating OOMs for them.
+ */
DO_OS_MALLOC_TEST(id);
}
#endif
@@ -23188,7 +23654,7 @@ SQLITE_PRIVATE int sqlite3OsOpen(
SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
DO_OS_MALLOC_TEST(0);
assert( dirSync==0 || dirSync==1 );
- return pVfs->xDelete(pVfs, zPath, dirSync);
+ return pVfs->xDelete!=0 ? pVfs->xDelete(pVfs, zPath, dirSync) : SQLITE_OK;
}
SQLITE_PRIVATE int sqlite3OsAccess(
sqlite3_vfs *pVfs,
@@ -23211,6 +23677,8 @@ SQLITE_PRIVATE int sqlite3OsFullPathname(
}
#ifndef SQLITE_OMIT_LOAD_EXTENSION
SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+ assert( zPath!=0 );
+ assert( strlen(zPath)<=SQLITE_MAX_PATHLEN ); /* tag-20210611-1 */
return pVfs->xDlOpen(pVfs, zPath);
}
SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
@@ -27478,7 +27946,6 @@ SQLITE_PRIVATE int sqlite3MallocInit(void){
if( sqlite3GlobalConfig.m.xMalloc==0 ){
sqlite3MemSetDefault();
}
- memset(&mem0, 0, sizeof(mem0));
mem0.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
if( sqlite3GlobalConfig.pPage==0 || sqlite3GlobalConfig.szPage<512
|| sqlite3GlobalConfig.nPage<=0 ){
@@ -27791,12 +28258,17 @@ SQLITE_PRIVATE void *sqlite3Realloc(void *pOld, u64 nBytes){
if( nOld==nNew ){
pNew = pOld;
}else if( sqlite3GlobalConfig.bMemstat ){
+ sqlite3_int64 nUsed;
sqlite3_mutex_enter(mem0.mutex);
sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes);
nDiff = nNew - nOld;
- if( nDiff>0 && sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >=
+ if( nDiff>0 && (nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED)) >=
mem0.alarmThreshold-nDiff ){
sqlite3MallocAlarm(nDiff);
+ if( mem0.hardLimit>0 && nUsed >= mem0.hardLimit - nDiff ){
+ sqlite3_mutex_leave(mem0.mutex);
+ return 0;
+ }
}
pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew);
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
@@ -28103,12 +28575,15 @@ SQLITE_PRIVATE void sqlite3OomClear(sqlite3 *db){
}
/*
-** Take actions at the end of an API call to indicate an OOM error
+** Take actions at the end of an API call to deal with error codes.
*/
-static SQLITE_NOINLINE int apiOomError(sqlite3 *db){
- sqlite3OomClear(db);
- sqlite3Error(db, SQLITE_NOMEM);
- return SQLITE_NOMEM_BKPT;
+static SQLITE_NOINLINE int apiHandleError(sqlite3 *db, int rc){
+ if( db->mallocFailed || rc==SQLITE_IOERR_NOMEM ){
+ sqlite3OomClear(db);
+ sqlite3Error(db, SQLITE_NOMEM);
+ return SQLITE_NOMEM_BKPT;
+ }
+ return rc & db->errMask;
}
/*
@@ -28130,8 +28605,8 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){
*/
assert( db!=0 );
assert( sqlite3_mutex_held(db->mutex) );
- if( db->mallocFailed || rc==SQLITE_IOERR_NOMEM ){
- return apiOomError(db);
+ if( db->mallocFailed || rc ){
+ return apiHandleError(db, rc);
}
return rc & db->errMask;
}
@@ -28169,7 +28644,7 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){
#define etSQLESCAPE2 10 /* Strings with '\'' doubled and enclosed in '',
NULL pointers replaced by SQL NULL. %Q */
#define etTOKEN 11 /* a pointer to a Token structure */
-#define etSRCLIST 12 /* a pointer to a SrcList */
+#define etSRCITEM 12 /* a pointer to a SrcItem */
#define etPOINTER 13 /* The %p conversion */
#define etSQLESCAPE3 14 /* %w -> Strings with '\"' doubled */
#define etORDINAL 15 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */
@@ -28235,10 +28710,16 @@ static const et_info fmtinfo[] = {
/* All the rest are undocumented and are for internal use only */
{ 'T', 0, 0, etTOKEN, 0, 0 },
- { 'S', 0, 0, etSRCLIST, 0, 0 },
+ { 'S', 0, 0, etSRCITEM, 0, 0 },
{ 'r', 10, 1, etORDINAL, 0, 0 },
};
+/* Notes:
+**
+** %S Takes a pointer to SrcItem. Shows name or database.name
+** %!S Like %S but prefer the zName over the zAlias
+*/
+
/* Floating point constants used for rounding */
static const double arRound[] = {
5.0e-01, 5.0e-02, 5.0e-03, 5.0e-04, 5.0e-05,
@@ -28567,11 +29048,10 @@ SQLITE_API void sqlite3_str_vappendf(
v = va_arg(ap,int);
}
if( v<0 ){
- if( v==SMALLEST_INT64 ){
- longvalue = ((u64)1)<<63;
- }else{
- longvalue = -v;
- }
+ testcase( v==SMALLEST_INT64 );
+ testcase( v==(-1) );
+ longvalue = ~v;
+ longvalue++;
prefix = '-';
}else{
longvalue = v;
@@ -28994,21 +29474,24 @@ SQLITE_API void sqlite3_str_vappendf(
length = width = 0;
break;
}
- case etSRCLIST: {
- SrcList *pSrc;
- int k;
- struct SrcList_item *pItem;
+ case etSRCITEM: {
+ SrcItem *pItem;
if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return;
- pSrc = va_arg(ap, SrcList*);
- k = va_arg(ap, int);
- pItem = &pSrc->a[k];
+ pItem = va_arg(ap, SrcItem*);
assert( bArgList==0 );
- assert( k>=0 && k<pSrc->nSrc );
- if( pItem->zDatabase ){
- sqlite3_str_appendall(pAccum, pItem->zDatabase);
- sqlite3_str_append(pAccum, ".", 1);
+ if( pItem->zAlias && !flag_altform2 ){
+ sqlite3_str_appendall(pAccum, pItem->zAlias);
+ }else if( pItem->zName ){
+ if( pItem->zDatabase ){
+ sqlite3_str_appendall(pAccum, pItem->zDatabase);
+ sqlite3_str_append(pAccum, ".", 1);
+ }
+ sqlite3_str_appendall(pAccum, pItem->zName);
+ }else if( pItem->zAlias ){
+ sqlite3_str_appendall(pAccum, pItem->zAlias);
+ }else if( ALWAYS(pItem->pSelect) ){
+ sqlite3_str_appendf(pAccum, "SUBQUERY %u", pItem->pSelect->selId);
}
- sqlite3_str_appendall(pAccum, pItem->zName);
length = width = 0;
break;
}
@@ -29062,7 +29545,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){
}else{
char *zOld = isMalloced(p) ? p->zText : 0;
i64 szNew = p->nChar;
- szNew += N + 1;
+ szNew += (sqlite3_int64)N + 1;
if( szNew+p->nChar<=p->mxAlloc ){
/* Force exponential buffer size growth as long as it does not overflow,
** to avoid having to call this routine too often */
@@ -29565,7 +30048,10 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m
}
sqlite3_str_appendf(&x, ")");
}
- sqlite3_str_appendf(&x, " AS");
+ if( pCte->pUse ){
+ sqlite3_str_appendf(&x, " (pUse=0x%p, nUse=%d)", pCte->pUse,
+ pCte->pUse->nUse);
+ }
sqlite3StrAccumFinish(&x);
sqlite3TreeViewItem(pView, zLine, i<pWith->nCte-1);
sqlite3TreeViewSelect(pView, pCte->pSelect, 0);
@@ -29581,29 +30067,25 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m
SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){
int i;
for(i=0; i<pSrc->nSrc; i++){
- const struct SrcList_item *pItem = &pSrc->a[i];
+ const SrcItem *pItem = &pSrc->a[i];
StrAccum x;
char zLine[100];
sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0);
- sqlite3_str_appendf(&x, "{%d:*}", pItem->iCursor);
- if( pItem->zDatabase ){
- sqlite3_str_appendf(&x, " %s.%s", pItem->zDatabase, pItem->zName);
- }else if( pItem->zName ){
- sqlite3_str_appendf(&x, " %s", pItem->zName);
- }
+ x.printfFlags |= SQLITE_PRINTF_INTERNAL;
+ sqlite3_str_appendf(&x, "{%d:*} %!S", pItem->iCursor, pItem);
if( pItem->pTab ){
sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx",
pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, pItem->colUsed);
}
- if( pItem->zAlias ){
- sqlite3_str_appendf(&x, " (AS %s)", pItem->zAlias);
- }
if( pItem->fg.jointype & JT_LEFT ){
sqlite3_str_appendf(&x, " LEFT-JOIN");
}
if( pItem->fg.fromDDL ){
sqlite3_str_appendf(&x, " DDL");
}
+ if( pItem->fg.isCte ){
+ sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse);
+ }
sqlite3StrAccumFinish(&x);
sqlite3TreeViewItem(pView, zLine, i<pSrc->nSrc-1);
if( pItem->pSelect ){
@@ -30161,6 +30643,14 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m
sqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
break;
}
+ case TK_ERROR: {
+ Expr tmp;
+ sqlite3TreeViewLine(pView, "ERROR");
+ tmp = *pExpr;
+ tmp.op = pExpr->op2;
+ sqlite3TreeViewExpr(pView, &tmp, 0);
+ break;
+ }
default: {
sqlite3TreeViewLine(pView, "op=%d", pExpr->op);
break;
@@ -30310,11 +30800,16 @@ SQLITE_API void sqlite3_randomness(int N, void *pBuf){
** number generator) not as an encryption device.
*/
if( !wsdPrng.isInit ){
+ sqlite3_vfs *pVfs = sqlite3_vfs_find(0);
int i;
char k[256];
wsdPrng.j = 0;
wsdPrng.i = 0;
- sqlite3OsRandomness(sqlite3_vfs_find(0), 256, k);
+ if( NEVER(pVfs==0) ){
+ memset(k, 0, sizeof(k));
+ }else{
+ sqlite3OsRandomness(pVfs, 256, k);
+ }
for(i=0; i<256; i++){
wsdPrng.s[i] = (u8)i;
}
@@ -31301,6 +31796,16 @@ SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code){
}
/*
+** The equivalent of sqlite3Error(db, SQLITE_OK). Clear the error state
+** and error message.
+*/
+SQLITE_PRIVATE void sqlite3ErrorClear(sqlite3 *db){
+ assert( db!=0 );
+ db->errCode = SQLITE_OK;
+ if( db->pErr ) sqlite3ValueSetNull(db->pErr);
+}
+
+/*
** Load the sqlite3.iSysErrno field if that is an appropriate thing
** to do based on the SQLite error code in rc.
*/
@@ -31867,6 +32372,7 @@ SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc
incr = 1;
}else{
incr = 2;
+ length &= ~1;
assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 );
for(i=3-enc; i<length && zNum[i]==0; i+=2){}
nonNum = i<length;
@@ -33223,7 +33729,7 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
/* 55 */ "Le" OpHelp("IF r[P3]<=r[P1]"),
/* 56 */ "Lt" OpHelp("IF r[P3]<r[P1]"),
/* 57 */ "Ge" OpHelp("IF r[P3]>=r[P1]"),
- /* 58 */ "ElseNotEq" OpHelp(""),
+ /* 58 */ "ElseEq" OpHelp(""),
/* 59 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"),
/* 60 */ "IncrVacuum" OpHelp(""),
/* 61 */ "VNext" OpHelp(""),
@@ -33245,102 +33751,106 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
/* 77 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"),
/* 78 */ "SCopy" OpHelp("r[P2]=r[P1]"),
/* 79 */ "IntCopy" OpHelp("r[P2]=r[P1]"),
- /* 80 */ "ResultRow" OpHelp("output=r[P1@P2]"),
- /* 81 */ "CollSeq" OpHelp(""),
- /* 82 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"),
- /* 83 */ "RealAffinity" OpHelp(""),
- /* 84 */ "Cast" OpHelp("affinity(r[P1])"),
- /* 85 */ "Permutation" OpHelp(""),
- /* 86 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"),
- /* 87 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"),
- /* 88 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"),
- /* 89 */ "Column" OpHelp("r[P3]=PX"),
- /* 90 */ "Affinity" OpHelp("affinity(r[P1@P2])"),
- /* 91 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"),
- /* 92 */ "Count" OpHelp("r[P2]=count()"),
- /* 93 */ "ReadCookie" OpHelp(""),
- /* 94 */ "SetCookie" OpHelp(""),
- /* 95 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"),
- /* 96 */ "OpenRead" OpHelp("root=P2 iDb=P3"),
- /* 97 */ "OpenWrite" OpHelp("root=P2 iDb=P3"),
- /* 98 */ "OpenDup" OpHelp(""),
- /* 99 */ "OpenAutoindex" OpHelp("nColumn=P2"),
- /* 100 */ "OpenEphemeral" OpHelp("nColumn=P2"),
- /* 101 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"),
- /* 102 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"),
- /* 103 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<<r[P1]"),
- /* 104 */ "ShiftRight" OpHelp("r[P3]=r[P2]>>r[P1]"),
- /* 105 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"),
- /* 106 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"),
- /* 107 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"),
- /* 108 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"),
- /* 109 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"),
- /* 110 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"),
- /* 111 */ "SorterOpen" OpHelp(""),
- /* 112 */ "BitNot" OpHelp("r[P2]= ~r[P1]"),
- /* 113 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"),
- /* 114 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"),
- /* 115 */ "String8" OpHelp("r[P2]='P4'"),
- /* 116 */ "Close" OpHelp(""),
- /* 117 */ "ColumnsUsed" OpHelp(""),
- /* 118 */ "SeekHit" OpHelp("seekHit=P2"),
- /* 119 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"),
- /* 120 */ "NewRowid" OpHelp("r[P2]=rowid"),
- /* 121 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"),
- /* 122 */ "Delete" OpHelp(""),
- /* 123 */ "ResetCount" OpHelp(""),
- /* 124 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"),
- /* 125 */ "SorterData" OpHelp("r[P2]=data"),
- /* 126 */ "RowData" OpHelp("r[P2]=data"),
- /* 127 */ "Rowid" OpHelp("r[P2]=rowid"),
- /* 128 */ "NullRow" OpHelp(""),
- /* 129 */ "SeekEnd" OpHelp(""),
- /* 130 */ "IdxInsert" OpHelp("key=r[P2]"),
- /* 131 */ "SorterInsert" OpHelp("key=r[P2]"),
- /* 132 */ "IdxDelete" OpHelp("key=r[P2@P3]"),
- /* 133 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"),
- /* 134 */ "IdxRowid" OpHelp("r[P2]=rowid"),
- /* 135 */ "FinishSeek" OpHelp(""),
- /* 136 */ "Destroy" OpHelp(""),
- /* 137 */ "Clear" OpHelp(""),
- /* 138 */ "ResetSorter" OpHelp(""),
- /* 139 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"),
- /* 140 */ "SqlExec" OpHelp(""),
- /* 141 */ "ParseSchema" OpHelp(""),
- /* 142 */ "LoadAnalysis" OpHelp(""),
- /* 143 */ "DropTable" OpHelp(""),
- /* 144 */ "DropIndex" OpHelp(""),
- /* 145 */ "DropTrigger" OpHelp(""),
- /* 146 */ "IntegrityCk" OpHelp(""),
- /* 147 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"),
- /* 148 */ "Param" OpHelp(""),
- /* 149 */ "FkCounter" OpHelp("fkctr[P1]+=P2"),
- /* 150 */ "Real" OpHelp("r[P2]=P4"),
- /* 151 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"),
- /* 152 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"),
- /* 153 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"),
- /* 154 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"),
- /* 155 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"),
- /* 156 */ "AggValue" OpHelp("r[P3]=value N=P2"),
- /* 157 */ "AggFinal" OpHelp("accum=r[P1] N=P2"),
- /* 158 */ "Expire" OpHelp(""),
- /* 159 */ "CursorLock" OpHelp(""),
- /* 160 */ "CursorUnlock" OpHelp(""),
- /* 161 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"),
- /* 162 */ "VBegin" OpHelp(""),
- /* 163 */ "VCreate" OpHelp(""),
- /* 164 */ "VDestroy" OpHelp(""),
- /* 165 */ "VOpen" OpHelp(""),
- /* 166 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"),
- /* 167 */ "VRename" OpHelp(""),
- /* 168 */ "Pagecount" OpHelp(""),
- /* 169 */ "MaxPgcnt" OpHelp(""),
- /* 170 */ "Trace" OpHelp(""),
- /* 171 */ "CursorHint" OpHelp(""),
- /* 172 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"),
- /* 173 */ "Noop" OpHelp(""),
- /* 174 */ "Explain" OpHelp(""),
- /* 175 */ "Abortable" OpHelp(""),
+ /* 80 */ "ChngCntRow" OpHelp("output=r[P1]"),
+ /* 81 */ "ResultRow" OpHelp("output=r[P1@P2]"),
+ /* 82 */ "CollSeq" OpHelp(""),
+ /* 83 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"),
+ /* 84 */ "RealAffinity" OpHelp(""),
+ /* 85 */ "Cast" OpHelp("affinity(r[P1])"),
+ /* 86 */ "Permutation" OpHelp(""),
+ /* 87 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"),
+ /* 88 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"),
+ /* 89 */ "ZeroOrNull" OpHelp("r[P2] = 0 OR NULL"),
+ /* 90 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"),
+ /* 91 */ "Column" OpHelp("r[P3]=PX"),
+ /* 92 */ "Affinity" OpHelp("affinity(r[P1@P2])"),
+ /* 93 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"),
+ /* 94 */ "Count" OpHelp("r[P2]=count()"),
+ /* 95 */ "ReadCookie" OpHelp(""),
+ /* 96 */ "SetCookie" OpHelp(""),
+ /* 97 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"),
+ /* 98 */ "OpenRead" OpHelp("root=P2 iDb=P3"),
+ /* 99 */ "OpenWrite" OpHelp("root=P2 iDb=P3"),
+ /* 100 */ "OpenDup" OpHelp(""),
+ /* 101 */ "OpenAutoindex" OpHelp("nColumn=P2"),
+ /* 102 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"),
+ /* 103 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"),
+ /* 104 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<<r[P1]"),
+ /* 105 */ "ShiftRight" OpHelp("r[P3]=r[P2]>>r[P1]"),
+ /* 106 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"),
+ /* 107 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"),
+ /* 108 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"),
+ /* 109 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"),
+ /* 110 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"),
+ /* 111 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"),
+ /* 112 */ "OpenEphemeral" OpHelp("nColumn=P2"),
+ /* 113 */ "BitNot" OpHelp("r[P2]= ~r[P1]"),
+ /* 114 */ "SorterOpen" OpHelp(""),
+ /* 115 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"),
+ /* 116 */ "String8" OpHelp("r[P2]='P4'"),
+ /* 117 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"),
+ /* 118 */ "Close" OpHelp(""),
+ /* 119 */ "ColumnsUsed" OpHelp(""),
+ /* 120 */ "SeekScan" OpHelp("Scan-ahead up to P1 rows"),
+ /* 121 */ "SeekHit" OpHelp("set P2<=seekHit<=P3"),
+ /* 122 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"),
+ /* 123 */ "NewRowid" OpHelp("r[P2]=rowid"),
+ /* 124 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"),
+ /* 125 */ "RowCell" OpHelp(""),
+ /* 126 */ "Delete" OpHelp(""),
+ /* 127 */ "ResetCount" OpHelp(""),
+ /* 128 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"),
+ /* 129 */ "SorterData" OpHelp("r[P2]=data"),
+ /* 130 */ "RowData" OpHelp("r[P2]=data"),
+ /* 131 */ "Rowid" OpHelp("r[P2]=rowid"),
+ /* 132 */ "NullRow" OpHelp(""),
+ /* 133 */ "SeekEnd" OpHelp(""),
+ /* 134 */ "IdxInsert" OpHelp("key=r[P2]"),
+ /* 135 */ "SorterInsert" OpHelp("key=r[P2]"),
+ /* 136 */ "IdxDelete" OpHelp("key=r[P2@P3]"),
+ /* 137 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"),
+ /* 138 */ "IdxRowid" OpHelp("r[P2]=rowid"),
+ /* 139 */ "FinishSeek" OpHelp(""),
+ /* 140 */ "Destroy" OpHelp(""),
+ /* 141 */ "Clear" OpHelp(""),
+ /* 142 */ "ResetSorter" OpHelp(""),
+ /* 143 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"),
+ /* 144 */ "SqlExec" OpHelp(""),
+ /* 145 */ "ParseSchema" OpHelp(""),
+ /* 146 */ "LoadAnalysis" OpHelp(""),
+ /* 147 */ "DropTable" OpHelp(""),
+ /* 148 */ "DropIndex" OpHelp(""),
+ /* 149 */ "DropTrigger" OpHelp(""),
+ /* 150 */ "IntegrityCk" OpHelp(""),
+ /* 151 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"),
+ /* 152 */ "Real" OpHelp("r[P2]=P4"),
+ /* 153 */ "Param" OpHelp(""),
+ /* 154 */ "FkCounter" OpHelp("fkctr[P1]+=P2"),
+ /* 155 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"),
+ /* 156 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"),
+ /* 157 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"),
+ /* 158 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"),
+ /* 159 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"),
+ /* 160 */ "AggValue" OpHelp("r[P3]=value N=P2"),
+ /* 161 */ "AggFinal" OpHelp("accum=r[P1] N=P2"),
+ /* 162 */ "Expire" OpHelp(""),
+ /* 163 */ "CursorLock" OpHelp(""),
+ /* 164 */ "CursorUnlock" OpHelp(""),
+ /* 165 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"),
+ /* 166 */ "VBegin" OpHelp(""),
+ /* 167 */ "VCreate" OpHelp(""),
+ /* 168 */ "VDestroy" OpHelp(""),
+ /* 169 */ "VOpen" OpHelp(""),
+ /* 170 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"),
+ /* 171 */ "VRename" OpHelp(""),
+ /* 172 */ "Pagecount" OpHelp(""),
+ /* 173 */ "MaxPgcnt" OpHelp(""),
+ /* 174 */ "Trace" OpHelp(""),
+ /* 175 */ "CursorHint" OpHelp(""),
+ /* 176 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"),
+ /* 177 */ "Noop" OpHelp(""),
+ /* 178 */ "Explain" OpHelp(""),
+ /* 179 */ "Abortable" OpHelp(""),
};
return azName[i];
}
@@ -33472,7 +33982,8 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
# if defined(__APPLE__) && ((__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) || \
(__IPHONE_OS_VERSION_MIN_REQUIRED > 2000))
# if (!defined(TARGET_OS_EMBEDDED) || (TARGET_OS_EMBEDDED==0)) \
- && (!defined(TARGET_IPHONE_SIMULATOR) || (TARGET_IPHONE_SIMULATOR==0))
+ && (!defined(TARGET_IPHONE_SIMULATOR) || (TARGET_IPHONE_SIMULATOR==0))\
+ && (!defined(TARGET_OS_MACCATALYST) || (TARGET_OS_MACCATALYST==0))
# undef HAVE_GETHOSTUUID
# define HAVE_GETHOSTUUID 1
# else
@@ -35092,6 +35603,9 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){
return rc;
}
+/* Forward declaration*/
+static int unixSleep(sqlite3_vfs*,int);
+
/*
** Set a posix-advisory-lock.
**
@@ -35121,7 +35635,7 @@ static int osSetPosixAdvisoryLock(
** generic posix, however, there is no such API. So we simply try the
** lock once every millisecond until either the timeout expires, or until
** the lock is obtained. */
- usleep(1000);
+ unixSleep(0,1000);
rc = osFcntl(h,F_SETLK,pLock);
tm--;
}
@@ -35692,6 +36206,7 @@ static int unixClose(sqlite3_file *id){
}
sqlite3_mutex_leave(pInode->pLockMutex);
releaseInodeInfo(pFile);
+ assert( pFile->pShm==0 );
rc = closeUnixFile(id);
unixLeaveMutex();
return rc;
@@ -36918,7 +37433,24 @@ static int unixRead(
if( got==amt ){
return SQLITE_OK;
}else if( got<0 ){
- /* lastErrno set by seekAndRead */
+ /* pFile->lastErrno has been set by seekAndRead().
+ ** Usually we return SQLITE_IOERR_READ here, though for some
+ ** kinds of errors we return SQLITE_IOERR_CORRUPTFS. The
+ ** SQLITE_IOERR_CORRUPTFS will be converted into SQLITE_CORRUPT
+ ** prior to returning to the application by the sqlite3ApiExit()
+ ** routine.
+ */
+ switch( pFile->lastErrno ){
+ case ERANGE:
+ case EIO:
+#ifdef ENXIO
+ case ENXIO:
+#endif
+#ifdef EDEVERR
+ case EDEVERR:
+#endif
+ return SQLITE_IOERR_CORRUPTFS;
+ }
return SQLITE_IOERR_READ;
}else{
storeLastErrno(pFile, 0); /* not a system error */
@@ -37477,6 +38009,7 @@ static void unixModeBit(unixFile *pFile, unsigned char mask, int *pArg){
/* Forward declaration */
static int unixGetTempname(int nBuf, char *zBuf);
+static int unixFcntlExternalReader(unixFile*, int*);
/*
** Information and control of an open file handle.
@@ -37593,6 +38126,10 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
return proxyFileControl(id,op,pArg);
}
#endif /* SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) */
+
+ case SQLITE_FCNTL_EXTERNAL_READER: {
+ return unixFcntlExternalReader((unixFile*)id, (int*)pArg);
+ }
}
return SQLITE_NOTFOUND;
}
@@ -37802,6 +38339,7 @@ struct unixShmNode {
char **apRegion; /* Array of mapped shared-memory regions */
int nRef; /* Number of unixShm objects pointing to this */
unixShm *pFirst; /* All unixShm objects pointing to this */
+ int aLock[SQLITE_SHM_NLOCK]; /* # shared locks on slot, -1==excl lock */
#ifdef SQLITE_DEBUG
u8 exclMask; /* Mask of exclusive locks held */
u8 sharedMask; /* Mask of shared locks held */
@@ -37838,6 +38376,40 @@ struct unixShm {
#define UNIX_SHM_DMS (UNIX_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */
/*
+** Use F_GETLK to check whether or not there are any readers with open
+** wal-mode transactions in other processes on database file pFile. If
+** no error occurs, return SQLITE_OK and set (*piOut) to 1 if there are
+** such transactions, or 0 otherwise. If an error occurs, return an
+** SQLite error code. The final value of *piOut is undefined in this
+** case.
+*/
+static int unixFcntlExternalReader(unixFile *pFile, int *piOut){
+ int rc = SQLITE_OK;
+ *piOut = 0;
+ if( pFile->pShm){
+ unixShmNode *pShmNode = pFile->pShm->pShmNode;
+ struct flock f;
+
+ memset(&f, 0, sizeof(f));
+ f.l_type = F_WRLCK;
+ f.l_whence = SEEK_SET;
+ f.l_start = UNIX_SHM_BASE + 3;
+ f.l_len = SQLITE_SHM_NLOCK - 3;
+
+ sqlite3_mutex_enter(pShmNode->pShmMutex);
+ if( osFcntl(pShmNode->hShm, F_GETLK, &f)<0 ){
+ rc = SQLITE_IOERR_LOCK;
+ }else{
+ *piOut = (f.l_type!=F_UNLCK);
+ }
+ sqlite3_mutex_leave(pShmNode->pShmMutex);
+ }
+
+ return rc;
+}
+
+
+/*
** Apply posix advisory locks for all bytes from ofst through ofst+n-1.
**
** Locks block if the mask is exactly UNIX_SHM_C and are non-blocking
@@ -38343,6 +38915,38 @@ shmpage_out:
}
/*
+** Check that the pShmNode->aLock[] array comports with the locking bitmasks
+** held by each client. Return true if it does, or false otherwise. This
+** is to be used in an assert(). e.g.
+**
+** assert( assertLockingArrayOk(pShmNode) );
+*/
+#ifdef SQLITE_DEBUG
+static int assertLockingArrayOk(unixShmNode *pShmNode){
+ unixShm *pX;
+ int aLock[SQLITE_SHM_NLOCK];
+ assert( sqlite3_mutex_held(pShmNode->pShmMutex) );
+
+ memset(aLock, 0, sizeof(aLock));
+ for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
+ int i;
+ for(i=0; i<SQLITE_SHM_NLOCK; i++){
+ if( pX->exclMask & (1<<i) ){
+ assert( aLock[i]==0 );
+ aLock[i] = -1;
+ }else if( pX->sharedMask & (1<<i) ){
+ assert( aLock[i]>=0 );
+ aLock[i]++;
+ }
+ }
+ }
+
+ assert( 0==memcmp(pShmNode->aLock, aLock, sizeof(aLock)) );
+ return (memcmp(pShmNode->aLock, aLock, sizeof(aLock))==0);
+}
+#endif
+
+/*
** Change the lock state for a shared-memory segment.
**
** Note that the relationship between SHAREd and EXCLUSIVE locks is a little
@@ -38358,10 +38962,10 @@ static int unixShmLock(
){
unixFile *pDbFd = (unixFile*)fd; /* Connection holding shared memory */
unixShm *p = pDbFd->pShm; /* The shared memory being locked */
- unixShm *pX; /* For looping over all siblings */
unixShmNode *pShmNode = p->pShmNode; /* The underlying file iNode */
int rc = SQLITE_OK; /* Result code */
u16 mask; /* Mask of locks to take or release */
+ int *aLock = pShmNode->aLock;
assert( pShmNode==pDbFd->pInode->pShmNode );
assert( pShmNode->pInode==pDbFd->pInode );
@@ -38400,78 +39004,76 @@ static int unixShmLock(
mask = (1<<(ofst+n)) - (1<<ofst);
assert( n>1 || mask==(1<<ofst) );
sqlite3_mutex_enter(pShmNode->pShmMutex);
+ assert( assertLockingArrayOk(pShmNode) );
if( flags & SQLITE_SHM_UNLOCK ){
- u16 allMask = 0; /* Mask of locks held by siblings */
+ if( (p->exclMask|p->sharedMask) & mask ){
+ int ii;
+ int bUnlock = 1;
- /* See if any siblings hold this same lock */
- for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
- if( pX==p ) continue;
- assert( (pX->exclMask & (p->exclMask|p->sharedMask))==0 );
- allMask |= pX->sharedMask;
- }
+ for(ii=ofst; ii<ofst+n; ii++){
+ if( aLock[ii]>((p->sharedMask & (1<<ii)) ? 1 : 0) ){
+ bUnlock = 0;
+ }
+ }
- /* Unlock the system-level locks */
- if( (mask & allMask)==0 ){
- rc = unixShmSystemLock(pDbFd, F_UNLCK, ofst+UNIX_SHM_BASE, n);
- }else{
- rc = SQLITE_OK;
- }
+ if( bUnlock ){
+ rc = unixShmSystemLock(pDbFd, F_UNLCK, ofst+UNIX_SHM_BASE, n);
+ if( rc==SQLITE_OK ){
+ memset(&aLock[ofst], 0, sizeof(int)*n);
+ }
+ }else if( ALWAYS(p->sharedMask & (1<<ofst)) ){
+ assert( n==1 && aLock[ofst]>1 );
+ aLock[ofst]--;
+ }
- /* Undo the local locks */
- if( rc==SQLITE_OK ){
- p->exclMask &= ~mask;
- p->sharedMask &= ~mask;
+ /* Undo the local locks */
+ if( rc==SQLITE_OK ){
+ p->exclMask &= ~mask;
+ p->sharedMask &= ~mask;
+ }
}
}else if( flags & SQLITE_SHM_SHARED ){
- u16 allShared = 0; /* Union of locks held by connections other than "p" */
-
- /* Find out which shared locks are already held by sibling connections.
- ** If any sibling already holds an exclusive lock, go ahead and return
- ** SQLITE_BUSY.
- */
- for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
- if( (pX->exclMask & mask)!=0 ){
+ assert( n==1 );
+ assert( (p->exclMask & (1<<ofst))==0 );
+ if( (p->sharedMask & mask)==0 ){
+ if( aLock[ofst]<0 ){
rc = SQLITE_BUSY;
- break;
- }
- allShared |= pX->sharedMask;
- }
-
- /* Get shared locks at the system level, if necessary */
- if( rc==SQLITE_OK ){
- if( (allShared & mask)==0 ){
+ }else if( aLock[ofst]==0 ){
rc = unixShmSystemLock(pDbFd, F_RDLCK, ofst+UNIX_SHM_BASE, n);
- }else{
- rc = SQLITE_OK;
}
- }
- /* Get the local shared locks */
- if( rc==SQLITE_OK ){
- p->sharedMask |= mask;
+ /* Get the local shared locks */
+ if( rc==SQLITE_OK ){
+ p->sharedMask |= mask;
+ aLock[ofst]++;
+ }
}
}else{
/* Make sure no sibling connections hold locks that will block this
- ** lock. If any do, return SQLITE_BUSY right away.
- */
- for(pX=pShmNode->pFirst; pX; pX=pX->pNext){
- if( (pX->exclMask & mask)!=0 || (pX->sharedMask & mask)!=0 ){
+ ** lock. If any do, return SQLITE_BUSY right away. */
+ int ii;
+ for(ii=ofst; ii<ofst+n; ii++){
+ assert( (p->sharedMask & mask)==0 );
+ if( ALWAYS((p->exclMask & (1<<ii))==0) && aLock[ii] ){
rc = SQLITE_BUSY;
break;
}
}
- /* Get the exclusive locks at the system level. Then if successful
- ** also mark the local connection as being locked.
- */
+ /* Get the exclusive locks at the system level. Then if successful
+ ** also update the in-memory values. */
if( rc==SQLITE_OK ){
rc = unixShmSystemLock(pDbFd, F_WRLCK, ofst+UNIX_SHM_BASE, n);
if( rc==SQLITE_OK ){
assert( (p->sharedMask & mask)==0 );
p->exclMask |= mask;
+ for(ii=ofst; ii<ofst+n; ii++){
+ aLock[ii] = -1;
+ }
}
}
}
+ assert( assertLockingArrayOk(pShmNode) );
sqlite3_mutex_leave(pShmNode->pShmMutex);
OSTRACE(("SHM-LOCK shmid-%d, pid-%d got %03x,%03x\n",
p->id, osGetpid(0), p->sharedMask, p->exclMask));
@@ -39848,7 +40450,27 @@ static int unixAccess(
}
/*
+** If the last component of the pathname in z[0]..z[j-1] is something
+** other than ".." then back it out and return true. If the last
+** component is empty or if it is ".." then return false.
+*/
+static int unixBackupDir(const char *z, int *pJ){
+ int j = *pJ;
+ int i;
+ if( j<=0 ) return 0;
+ for(i=j-1; i>0 && z[i-1]!='/'; i--){}
+ if( i==0 ) return 0;
+ if( z[i]=='.' && i==j-2 && z[i+1]=='.' ) return 0;
+ *pJ = i-1;
+ return 1;
+}
+
+/*
+** Convert a relative pathname into a full pathname. Also
+** simplify the pathname as follows:
**
+** Remove all instances of /./
+** Remove all isntances of /X/../ for any X
*/
static int mkFullPathname(
const char *zPath, /* Input path */
@@ -39857,6 +40479,7 @@ static int mkFullPathname(
){
int nPath = sqlite3Strlen30(zPath);
int iOff = 0;
+ int i, j;
if( zPath[0]!='/' ){
if( osGetcwd(zOut, nOut-2)==0 ){
return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath);
@@ -39871,6 +40494,41 @@ static int mkFullPathname(
return SQLITE_CANTOPEN_BKPT;
}
sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath);
+
+ /* Remove duplicate '/' characters. Except, two // at the beginning
+ ** of a pathname is allowed since this is important on windows. */
+ for(i=j=1; zOut[i]; i++){
+ zOut[j++] = zOut[i];
+ while( zOut[i]=='/' && zOut[i+1]=='/' ) i++;
+ }
+ zOut[j] = 0;
+
+ assert( zOut[0]=='/' );
+ for(i=j=0; zOut[i]; i++){
+ if( zOut[i]=='/' ){
+ /* Skip over internal "/." directory components */
+ if( zOut[i+1]=='.' && zOut[i+2]=='/' ){
+ i += 1;
+ continue;
+ }
+
+ /* If this is a "/.." directory component then back out the
+ ** previous term of the directory if it is something other than "..".
+ */
+ if( zOut[i+1]=='.'
+ && zOut[i+2]=='.'
+ && zOut[i+3]=='/'
+ && unixBackupDir(zOut, &j)
+ ){
+ i += 2;
+ continue;
+ }
+ }
+ if( ALWAYS(j>=0) ) zOut[j] = zOut[i];
+ j++;
+ }
+ if( NEVER(j==0) ) zOut[j++] = '/';
+ zOut[j] = 0;
return SQLITE_OK;
}
@@ -40091,7 +40749,8 @@ static int unixSleep(sqlite3_vfs *NotUsed, int microseconds){
UNUSED_PARAMETER(NotUsed);
return microseconds;
#elif defined(HAVE_USLEEP) && HAVE_USLEEP
- usleep(microseconds);
+ if( microseconds>=1000000 ) sleep(microseconds/1000000);
+ if( microseconds%1000000 ) usleep(microseconds%1000000);
UNUSED_PARAMETER(NotUsed);
return microseconds;
#else
@@ -40664,7 +41323,7 @@ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){
if( nTries==1 ){
conchModTime = buf.st_mtimespec;
- usleep(500000); /* wait 0.5 sec and try the lock again*/
+ unixSleep(0,500000); /* wait 0.5 sec and try the lock again*/
continue;
}
@@ -40690,7 +41349,7 @@ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){
/* don't break the lock on short read or a version mismatch */
return SQLITE_BUSY;
}
- usleep(10000000); /* wait 10 sec and try the lock again */
+ unixSleep(0,10000000); /* wait 10 sec and try the lock again */
continue;
}
@@ -41466,6 +42125,25 @@ SQLITE_API int sqlite3_os_init(void){
sqlite3_vfs_register(&aVfs[i], i==0);
}
unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
+
+#ifndef SQLITE_OMIT_WAL
+ /* Validate lock assumptions */
+ assert( SQLITE_SHM_NLOCK==8 ); /* Number of available locks */
+ assert( UNIX_SHM_BASE==120 ); /* Start of locking area */
+ /* Locks:
+ ** WRITE UNIX_SHM_BASE 120
+ ** CKPT UNIX_SHM_BASE+1 121
+ ** RECOVER UNIX_SHM_BASE+2 122
+ ** READ-0 UNIX_SHM_BASE+3 123
+ ** READ-1 UNIX_SHM_BASE+4 124
+ ** READ-2 UNIX_SHM_BASE+5 125
+ ** READ-3 UNIX_SHM_BASE+6 126
+ ** READ-4 UNIX_SHM_BASE+7 127
+ ** DMS UNIX_SHM_BASE+8 128
+ */
+ assert( UNIX_SHM_DMS==128 ); /* Byte offset of the deadman-switch */
+#endif
+
return SQLITE_OK;
}
@@ -46811,7 +47489,11 @@ static int winOpen(
dwCreationDisposition = OPEN_EXISTING;
}
- dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ if( 0==sqlite3_uri_boolean(zName, "exclusive", 0) ){
+ dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ }else{
+ dwShareMode = 0;
+ }
if( isDelete ){
#if SQLITE_OS_WINCE
@@ -47855,32 +48537,89 @@ SQLITE_API int sqlite3_os_end(void){
** sqlite3_deserialize().
*/
/* #include "sqliteInt.h" */
-#ifdef SQLITE_ENABLE_DESERIALIZE
+#ifndef SQLITE_OMIT_DESERIALIZE
/*
** Forward declaration of objects used by this utility
*/
typedef struct sqlite3_vfs MemVfs;
typedef struct MemFile MemFile;
+typedef struct MemStore MemStore;
/* Access to a lower-level VFS that (might) implement dynamic loading,
** access to randomness, etc.
*/
#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData))
-/* An open file */
-struct MemFile {
- sqlite3_file base; /* IO methods */
+/* Storage for a memdb file.
+**
+** An memdb object can be shared or separate. Shared memdb objects can be
+** used by more than one database connection. Mutexes are used by shared
+** memdb objects to coordinate access. Separate memdb objects are only
+** connected to a single database connection and do not require additional
+** mutexes.
+**
+** Shared memdb objects have .zFName!=0 and .pMutex!=0. They are created
+** using "file:/name?vfs=memdb". The first character of the name must be
+** "/" or else the object will be a separate memdb object. All shared
+** memdb objects are stored in memdb_g.apMemStore[] in an arbitrary order.
+**
+** Separate memdb objects are created using a name that does not begin
+** with "/" or using sqlite3_deserialize().
+**
+** Access rules for shared MemStore objects:
+**
+** * .zFName is initialized when the object is created and afterwards
+** is unchanged until the object is destroyed. So it can be accessed
+** at any time as long as we know the object is not being destroyed,
+** which means while either the SQLITE_MUTEX_STATIC_VFS1 or
+** .pMutex is held or the object is not part of memdb_g.apMemStore[].
+**
+** * Can .pMutex can only be changed while holding the
+** SQLITE_MUTEX_STATIC_VFS1 mutex or while the object is not part
+** of memdb_g.apMemStore[].
+**
+** * Other fields can only be changed while holding the .pMutex mutex
+** or when the .nRef is less than zero and the object is not part of
+** memdb_g.apMemStore[].
+**
+** * The .aData pointer has the added requirement that it can can only
+** be changed (for resizing) when nMmap is zero.
+**
+*/
+struct MemStore {
sqlite3_int64 sz; /* Size of the file */
sqlite3_int64 szAlloc; /* Space allocated to aData */
sqlite3_int64 szMax; /* Maximum allowed size of the file */
unsigned char *aData; /* content of the file */
+ sqlite3_mutex *pMutex; /* Used by shared stores only */
int nMmap; /* Number of memory mapped pages */
unsigned mFlags; /* Flags */
+ int nRdLock; /* Number of readers */
+ int nWrLock; /* Number of writers. (Always 0 or 1) */
+ int nRef; /* Number of users of this MemStore */
+ char *zFName; /* The filename for shared stores */
+};
+
+/* An open file */
+struct MemFile {
+ sqlite3_file base; /* IO methods */
+ MemStore *pStore; /* The storage */
int eLock; /* Most recent lock against this file */
};
/*
+** File-scope variables for holding the memdb files that are accessible
+** to multiple database connections in separate threads.
+**
+** Must hold SQLITE_MUTEX_STATIC_VFS1 to access any part of this object.
+*/
+static struct MemFS {
+ int nMemStore; /* Number of shared MemStore objects */
+ MemStore **apMemStore; /* Array of all shared MemStore objects */
+} memdb_g;
+
+/*
** Methods for MemFile
*/
static int memdbClose(sqlite3_file*);
@@ -47933,7 +48672,10 @@ static sqlite3_vfs memdb_vfs = {
memdbSleep, /* xSleep */
0, /* memdbCurrentTime, */ /* xCurrentTime */
memdbGetLastError, /* xGetLastError */
- memdbCurrentTimeInt64 /* xCurrentTimeInt64 */
+ memdbCurrentTimeInt64, /* xCurrentTimeInt64 */
+ 0, /* xSetSystemCall */
+ 0, /* xGetSystemCall */
+ 0, /* xNextSystemCall */
};
static const sqlite3_io_methods memdb_io_methods = {
@@ -47958,17 +48700,68 @@ static const sqlite3_io_methods memdb_io_methods = {
memdbUnfetch /* xUnfetch */
};
+/*
+** Enter/leave the mutex on a MemStore
+*/
+#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
+static void memdbEnter(MemStore *p){
+ UNUSED_PARAMETER(p);
+}
+static void memdbLeave(MemStore *p){
+ UNUSED_PARAMETER(p);
+}
+#else
+static void memdbEnter(MemStore *p){
+ sqlite3_mutex_enter(p->pMutex);
+}
+static void memdbLeave(MemStore *p){
+ sqlite3_mutex_leave(p->pMutex);
+}
+#endif
+
/*
** Close an memdb-file.
-**
-** The pData pointer is owned by the application, so there is nothing
-** to free.
+** Free the underlying MemStore object when its refcount drops to zero
+** or less.
*/
static int memdbClose(sqlite3_file *pFile){
- MemFile *p = (MemFile *)pFile;
- if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ) sqlite3_free(p->aData);
+ MemStore *p = ((MemFile*)pFile)->pStore;
+ if( p->zFName ){
+ int i;
+#ifndef SQLITE_MUTEX_OMIT
+ sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
+#endif
+ sqlite3_mutex_enter(pVfsMutex);
+ for(i=0; ALWAYS(i<memdb_g.nMemStore); i++){
+ if( memdb_g.apMemStore[i]==p ){
+ memdbEnter(p);
+ if( p->nRef==1 ){
+ memdb_g.apMemStore[i] = memdb_g.apMemStore[--memdb_g.nMemStore];
+ if( memdb_g.nMemStore==0 ){
+ sqlite3_free(memdb_g.apMemStore);
+ memdb_g.apMemStore = 0;
+ }
+ }
+ break;
+ }
+ }
+ sqlite3_mutex_leave(pVfsMutex);
+ }else{
+ memdbEnter(p);
+ }
+ p->nRef--;
+ if( p->nRef<=0 ){
+ if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ){
+ sqlite3_free(p->aData);
+ }
+ memdbLeave(p);
+ sqlite3_mutex_free(p->pMutex);
+ sqlite3_free(p);
+ }else{
+ memdbLeave(p);
+ }
return SQLITE_OK;
}
@@ -47981,20 +48774,23 @@ static int memdbRead(
int iAmt,
sqlite_int64 iOfst
){
- MemFile *p = (MemFile *)pFile;
+ MemStore *p = ((MemFile*)pFile)->pStore;
+ memdbEnter(p);
if( iOfst+iAmt>p->sz ){
memset(zBuf, 0, iAmt);
if( iOfst<p->sz ) memcpy(zBuf, p->aData+iOfst, p->sz - iOfst);
+ memdbLeave(p);
return SQLITE_IOERR_SHORT_READ;
}
memcpy(zBuf, p->aData+iOfst, iAmt);
+ memdbLeave(p);
return SQLITE_OK;
}
/*
** Try to enlarge the memory allocation to hold at least sz bytes
*/
-static int memdbEnlarge(MemFile *p, sqlite3_int64 newSz){
+static int memdbEnlarge(MemStore *p, sqlite3_int64 newSz){
unsigned char *pNew;
if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || p->nMmap>0 ){
return SQLITE_FULL;
@@ -48005,7 +48801,7 @@ static int memdbEnlarge(MemFile *p, sqlite3_int64 newSz){
newSz *= 2;
if( newSz>p->szMax ) newSz = p->szMax;
pNew = sqlite3Realloc(p->aData, newSz);
- if( pNew==0 ) return SQLITE_NOMEM;
+ if( pNew==0 ) return SQLITE_IOERR_NOMEM;
p->aData = pNew;
p->szAlloc = newSz;
return SQLITE_OK;
@@ -48020,19 +48816,27 @@ static int memdbWrite(
int iAmt,
sqlite_int64 iOfst
){
- MemFile *p = (MemFile *)pFile;
- if( NEVER(p->mFlags & SQLITE_DESERIALIZE_READONLY) ) return SQLITE_READONLY;
+ MemStore *p = ((MemFile*)pFile)->pStore;
+ memdbEnter(p);
+ if( NEVER(p->mFlags & SQLITE_DESERIALIZE_READONLY) ){
+ /* Can't happen: memdbLock() will return SQLITE_READONLY before
+ ** reaching this point */
+ memdbLeave(p);
+ return SQLITE_IOERR_WRITE;
+ }
if( iOfst+iAmt>p->sz ){
int rc;
if( iOfst+iAmt>p->szAlloc
&& (rc = memdbEnlarge(p, iOfst+iAmt))!=SQLITE_OK
){
+ memdbLeave(p);
return rc;
}
if( iOfst>p->sz ) memset(p->aData+p->sz, 0, iOfst-p->sz);
p->sz = iOfst+iAmt;
}
memcpy(p->aData+iOfst, z, iAmt);
+ memdbLeave(p);
return SQLITE_OK;
}
@@ -48044,16 +48848,24 @@ static int memdbWrite(
** the size of a file, never to increase the size.
*/
static int memdbTruncate(sqlite3_file *pFile, sqlite_int64 size){
- MemFile *p = (MemFile *)pFile;
- if( NEVER(size>p->sz) ) return SQLITE_FULL;
- p->sz = size;
- return SQLITE_OK;
+ MemStore *p = ((MemFile*)pFile)->pStore;
+ int rc = SQLITE_OK;
+ memdbEnter(p);
+ if( NEVER(size>p->sz) ){
+ rc = SQLITE_FULL;
+ }else{
+ p->sz = size;
+ }
+ memdbLeave(p);
+ return rc;
}
/*
** Sync an memdb-file.
*/
static int memdbSync(sqlite3_file *pFile, int flags){
+ UNUSED_PARAMETER(pFile);
+ UNUSED_PARAMETER(flags);
return SQLITE_OK;
}
@@ -48061,8 +48873,10 @@ static int memdbSync(sqlite3_file *pFile, int flags){
** Return the current file-size of an memdb-file.
*/
static int memdbFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
- MemFile *p = (MemFile *)pFile;
+ MemStore *p = ((MemFile*)pFile)->pStore;
+ memdbEnter(p);
*pSize = p->sz;
+ memdbLeave(p);
return SQLITE_OK;
}
@@ -48070,19 +48884,48 @@ static int memdbFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
** Lock an memdb-file.
*/
static int memdbLock(sqlite3_file *pFile, int eLock){
- MemFile *p = (MemFile *)pFile;
- if( eLock>SQLITE_LOCK_SHARED
- && (p->mFlags & SQLITE_DESERIALIZE_READONLY)!=0
- ){
- return SQLITE_READONLY;
+ MemFile *pThis = (MemFile*)pFile;
+ MemStore *p = pThis->pStore;
+ int rc = SQLITE_OK;
+ if( eLock==pThis->eLock ) return SQLITE_OK;
+ memdbEnter(p);
+ if( eLock>SQLITE_LOCK_SHARED ){
+ if( p->mFlags & SQLITE_DESERIALIZE_READONLY ){
+ rc = SQLITE_READONLY;
+ }else if( pThis->eLock<=SQLITE_LOCK_SHARED ){
+ if( p->nWrLock ){
+ rc = SQLITE_BUSY;
+ }else{
+ p->nWrLock = 1;
+ }
+ }
+ }else if( eLock==SQLITE_LOCK_SHARED ){
+ if( pThis->eLock > SQLITE_LOCK_SHARED ){
+ assert( p->nWrLock==1 );
+ p->nWrLock = 0;
+ }else if( p->nWrLock ){
+ rc = SQLITE_BUSY;
+ }else{
+ p->nRdLock++;
+ }
+ }else{
+ assert( eLock==SQLITE_LOCK_NONE );
+ if( pThis->eLock>SQLITE_LOCK_SHARED ){
+ assert( p->nWrLock==1 );
+ p->nWrLock = 0;
+ }
+ assert( p->nRdLock>0 );
+ p->nRdLock--;
}
- p->eLock = eLock;
- return SQLITE_OK;
+ if( rc==SQLITE_OK ) pThis->eLock = eLock;
+ memdbLeave(p);
+ return rc;
}
-#if 0 /* Never used because memdbAccess() always returns false */
+#if 0
/*
-** Check if another file-handle holds a RESERVED lock on an memdb-file.
+** This interface is only used for crash recovery, which does not
+** occur on an in-memory database.
*/
static int memdbCheckReservedLock(sqlite3_file *pFile, int *pResOut){
*pResOut = 0;
@@ -48090,12 +48933,14 @@ static int memdbCheckReservedLock(sqlite3_file *pFile, int *pResOut){
}
#endif
+
/*
** File control method. For custom operations on an memdb-file.
*/
static int memdbFileControl(sqlite3_file *pFile, int op, void *pArg){
- MemFile *p = (MemFile *)pFile;
+ MemStore *p = ((MemFile*)pFile)->pStore;
int rc = SQLITE_NOTFOUND;
+ memdbEnter(p);
if( op==SQLITE_FCNTL_VFSNAME ){
*(char**)pArg = sqlite3_mprintf("memdb(%p,%lld)", p->aData, p->sz);
rc = SQLITE_OK;
@@ -48113,6 +48958,7 @@ static int memdbFileControl(sqlite3_file *pFile, int op, void *pArg){
*(sqlite3_int64*)pArg = iLimit;
rc = SQLITE_OK;
}
+ memdbLeave(p);
return rc;
}
@@ -48129,6 +48975,7 @@ static int memdbSectorSize(sqlite3_file *pFile){
** Return the device characteristic flags supported by an memdb-file.
*/
static int memdbDeviceCharacteristics(sqlite3_file *pFile){
+ UNUSED_PARAMETER(pFile);
return SQLITE_IOCAP_ATOMIC |
SQLITE_IOCAP_POWERSAFE_OVERWRITE |
SQLITE_IOCAP_SAFE_APPEND |
@@ -48142,20 +48989,26 @@ static int memdbFetch(
int iAmt,
void **pp
){
- MemFile *p = (MemFile *)pFile;
+ MemStore *p = ((MemFile*)pFile)->pStore;
+ memdbEnter(p);
if( iOfst+iAmt>p->sz ){
*pp = 0;
}else{
p->nMmap++;
*pp = (void*)(p->aData + iOfst);
}
+ memdbLeave(p);
return SQLITE_OK;
}
/* Release a memory-mapped page */
static int memdbUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
- MemFile *p = (MemFile *)pFile;
+ MemStore *p = ((MemFile*)pFile)->pStore;
+ UNUSED_PARAMETER(iOfst);
+ UNUSED_PARAMETER(pPage);
+ memdbEnter(p);
p->nMmap--;
+ memdbLeave(p);
return SQLITE_OK;
}
@@ -48165,20 +49018,79 @@ static int memdbUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
static int memdbOpen(
sqlite3_vfs *pVfs,
const char *zName,
- sqlite3_file *pFile,
+ sqlite3_file *pFd,
int flags,
int *pOutFlags
){
- MemFile *p = (MemFile*)pFile;
+ MemFile *pFile = (MemFile*)pFd;
+ MemStore *p = 0;
+ int szName;
if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){
- return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFile, flags, pOutFlags);
+ return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFd, flags, pOutFlags);
}
- memset(p, 0, sizeof(*p));
- p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE;
+ memset(pFile, 0, sizeof(*p));
+ szName = sqlite3Strlen30(zName);
+ if( szName>1 && zName[0]=='/' ){
+ int i;
+#ifndef SQLITE_MUTEX_OMIT
+ sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
+#endif
+ sqlite3_mutex_enter(pVfsMutex);
+ for(i=0; i<memdb_g.nMemStore; i++){
+ if( strcmp(memdb_g.apMemStore[i]->zFName,zName)==0 ){
+ p = memdb_g.apMemStore[i];
+ break;
+ }
+ }
+ if( p==0 ){
+ MemStore **apNew;
+ p = sqlite3Malloc( sizeof(*p) + szName + 3 );
+ if( p==0 ){
+ sqlite3_mutex_leave(pVfsMutex);
+ return SQLITE_NOMEM;
+ }
+ apNew = sqlite3Realloc(memdb_g.apMemStore,
+ sizeof(apNew[0])*(memdb_g.nMemStore+1) );
+ if( apNew==0 ){
+ sqlite3_free(p);
+ sqlite3_mutex_leave(pVfsMutex);
+ return SQLITE_NOMEM;
+ }
+ apNew[memdb_g.nMemStore++] = p;
+ memdb_g.apMemStore = apNew;
+ memset(p, 0, sizeof(*p));
+ p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE|SQLITE_DESERIALIZE_FREEONCLOSE;
+ p->szMax = sqlite3GlobalConfig.mxMemdbSize;
+ p->zFName = (char*)&p[1];
+ memcpy(p->zFName, zName, szName+1);
+ p->pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ if( p->pMutex==0 ){
+ memdb_g.nMemStore--;
+ sqlite3_free(p);
+ sqlite3_mutex_leave(pVfsMutex);
+ return SQLITE_NOMEM;
+ }
+ p->nRef = 1;
+ memdbEnter(p);
+ }else{
+ memdbEnter(p);
+ p->nRef++;
+ }
+ sqlite3_mutex_leave(pVfsMutex);
+ }else{
+ p = sqlite3Malloc( sizeof(*p) );
+ if( p==0 ){
+ return SQLITE_NOMEM;
+ }
+ memset(p, 0, sizeof(*p));
+ p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE;
+ p->szMax = sqlite3GlobalConfig.mxMemdbSize;
+ }
+ pFile->pStore = p;
assert( pOutFlags!=0 ); /* True because flags==SQLITE_OPEN_MAIN_DB */
*pOutFlags = flags | SQLITE_OPEN_MEMORY;
- pFile->pMethods = &memdb_io_methods;
- p->szMax = sqlite3GlobalConfig.mxMemdbSize;
+ pFd->pMethods = &memdb_io_methods;
+ memdbLeave(p);
return SQLITE_OK;
}
@@ -48206,6 +49118,9 @@ static int memdbAccess(
int flags,
int *pResOut
){
+ UNUSED_PARAMETER(pVfs);
+ UNUSED_PARAMETER(zPath);
+ UNUSED_PARAMETER(flags);
*pResOut = 0;
return SQLITE_OK;
}
@@ -48221,6 +49136,7 @@ static int memdbFullPathname(
int nOut,
char *zOut
){
+ UNUSED_PARAMETER(pVfs);
sqlite3_snprintf(nOut, zOut, "%s", zPath);
return SQLITE_OK;
}
@@ -48293,9 +49209,14 @@ static int memdbCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){
*/
static MemFile *memdbFromDbSchema(sqlite3 *db, const char *zSchema){
MemFile *p = 0;
+ MemStore *pStore;
int rc = sqlite3_file_control(db, zSchema, SQLITE_FCNTL_FILE_POINTER, &p);
if( rc ) return 0;
if( p->base.pMethods!=&memdb_io_methods ) return 0;
+ pStore = p->pStore;
+ memdbEnter(pStore);
+ if( pStore->zFName!=0 ) p = 0;
+ memdbLeave(pStore);
return p;
}
@@ -48331,12 +49252,14 @@ SQLITE_API unsigned char *sqlite3_serialize(
if( piSize ) *piSize = -1;
if( iDb<0 ) return 0;
if( p ){
- if( piSize ) *piSize = p->sz;
+ MemStore *pStore = p->pStore;
+ assert( pStore->pMutex==0 );
+ if( piSize ) *piSize = pStore->sz;
if( mFlags & SQLITE_SERIALIZE_NOCOPY ){
- pOut = p->aData;
+ pOut = pStore->aData;
}else{
- pOut = sqlite3_malloc64( p->sz );
- if( pOut ) memcpy(pOut, p->aData, p->sz);
+ pOut = sqlite3_malloc64( pStore->sz );
+ if( pOut ) memcpy(pOut, pStore->aData, pStore->sz);
}
return pOut;
}
@@ -48411,8 +49334,12 @@ SQLITE_API int sqlite3_deserialize(
goto end_deserialize;
}
zSql = sqlite3_mprintf("ATTACH x AS %Q", zSchema);
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ sqlite3_free(zSql);
+ }
if( rc ) goto end_deserialize;
db->init.iDb = (u8)iDb;
db->init.reopenMemdb = 1;
@@ -48426,19 +49353,24 @@ SQLITE_API int sqlite3_deserialize(
if( p==0 ){
rc = SQLITE_ERROR;
}else{
- p->aData = pData;
- p->sz = szDb;
- p->szAlloc = szBuf;
- p->szMax = szBuf;
- if( p->szMax<sqlite3GlobalConfig.mxMemdbSize ){
- p->szMax = sqlite3GlobalConfig.mxMemdbSize;
+ MemStore *pStore = p->pStore;
+ pStore->aData = pData;
+ pData = 0;
+ pStore->sz = szDb;
+ pStore->szAlloc = szBuf;
+ pStore->szMax = szBuf;
+ if( pStore->szMax<sqlite3GlobalConfig.mxMemdbSize ){
+ pStore->szMax = sqlite3GlobalConfig.mxMemdbSize;
}
- p->mFlags = mFlags;
+ pStore->mFlags = mFlags;
rc = SQLITE_OK;
}
end_deserialize:
sqlite3_finalize(pStmt);
+ if( pData && (mFlags & SQLITE_DESERIALIZE_FREEONCLOSE)!=0 ){
+ sqlite3_free(pData);
+ }
sqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -48449,7 +49381,9 @@ end_deserialize:
*/
SQLITE_PRIVATE int sqlite3MemdbInit(void){
sqlite3_vfs *pLower = sqlite3_vfs_find(0);
- int sz = pLower->szOsFile;
+ unsigned int sz;
+ if( NEVER(pLower==0) ) return SQLITE_ERROR;
+ sz = pLower->szOsFile;
memdb_vfs.pAppData = pLower;
/* The following conditional can only be true when compiled for
** Windows x86 and SQLITE_MAX_MMAP_SIZE=0. We always leave
@@ -48459,7 +49393,7 @@ SQLITE_PRIVATE int sqlite3MemdbInit(void){
memdb_vfs.szOsFile = sz;
return sqlite3_vfs_register(&memdb_vfs, 0);
}
-#endif /* SQLITE_ENABLE_DESERIALIZE */
+#endif /* SQLITE_OMIT_DESERIALIZE */
/************** End of memdb.c ***********************************************/
/************** Begin file bitvec.c ******************************************/
@@ -50225,6 +51159,7 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache, int benignMalloc){
p->page.pExtra = &p[1];
p->isBulkLocal = 0;
p->isAnchor = 0;
+ p->pLruPrev = 0; /* Initializing this saves a valgrind error */
}
(*pCache->pnPurgeable)++;
return p;
@@ -52143,6 +53078,7 @@ struct PagerSavepoint {
Bitvec *pInSavepoint; /* Set of pages in this savepoint */
Pgno nOrig; /* Original number of pages in file */
Pgno iSubRec; /* Index of first record in sub-journal */
+ int bTruncateOnRelease; /* If stmt journal may be truncated on RELEASE */
#ifndef SQLITE_OMIT_WAL
u32 aWalData[WAL_SAVEPOINT_NDATA]; /* WAL savepoint context */
#endif
@@ -52778,6 +53714,9 @@ static int subjRequiresPage(PgHdr *pPg){
for(i=0; i<pPager->nSavepoint; i++){
p = &pPager->aSavepoint[i];
if( p->nOrig>=pgno && 0==sqlite3BitvecTestNotNull(p->pInSavepoint, pgno) ){
+ for(i=i+1; i<pPager->nSavepoint; i++){
+ pPager->aSavepoint[i].bTruncateOnRelease = 0;
+ }
return 1;
}
}
@@ -54194,6 +55133,7 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){
i64 nSuperJournal; /* Size of super-journal file */
char *zJournal; /* Pointer to one journal within MJ file */
char *zSuperPtr; /* Space to hold super-journal filename */
+ char *zFree = 0; /* Free this buffer */
int nSuperPtr; /* Amount of space allocated to zSuperPtr[] */
/* Allocate space for both the pJournal and pSuper file descriptors.
@@ -54218,11 +55158,13 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){
rc = sqlite3OsFileSize(pSuper, &nSuperJournal);
if( rc!=SQLITE_OK ) goto delsuper_out;
nSuperPtr = pVfs->mxPathname+1;
- zSuperJournal = sqlite3Malloc(nSuperJournal + nSuperPtr + 2);
- if( !zSuperJournal ){
+ zFree = sqlite3Malloc(4 + nSuperJournal + nSuperPtr + 2);
+ if( !zFree ){
rc = SQLITE_NOMEM_BKPT;
goto delsuper_out;
}
+ zFree[0] = zFree[1] = zFree[2] = zFree[3] = 0;
+ zSuperJournal = &zFree[4];
zSuperPtr = &zSuperJournal[nSuperJournal+2];
rc = sqlite3OsRead(pSuper, zSuperJournal, (int)nSuperJournal, 0);
if( rc!=SQLITE_OK ) goto delsuper_out;
@@ -54270,7 +55212,7 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){
rc = sqlite3OsDelete(pVfs, zSuper, 0);
delsuper_out:
- sqlite3_free(zSuperJournal);
+ sqlite3_free(zFree);
if( pSuper ){
sqlite3OsClose(pSuper);
assert( !isOpen(pJournal) );
@@ -54608,7 +55550,11 @@ end_playback:
pPager->changeCountDone = pPager->tempFile;
if( rc==SQLITE_OK ){
- zSuper = pPager->pTmpSpace;
+ /* Leave 4 bytes of space before the super-journal filename in memory.
+ ** This is because it may end up being passed to sqlite3OsOpen(), in
+ ** which case it requires 4 0x00 bytes in memory immediately before
+ ** the filename. */
+ zSuper = &pPager->pTmpSpace[4];
rc = readSuperJournal(pPager->jfd, zSuper, pPager->pVfs->mxPathname+1);
testcase( rc!=SQLITE_OK );
}
@@ -54625,6 +55571,8 @@ end_playback:
/* If there was a super-journal and this routine will return success,
** see if it is possible to delete the super-journal.
*/
+ assert( zSuper==&pPager->pTmpSpace[4] );
+ memset(&zSuper[-4], 0, 4);
rc = pager_delsuper(pPager, zSuper);
testcase( rc!=SQLITE_OK );
}
@@ -55631,7 +56579,8 @@ static void assertTruncateConstraint(Pager *pPager){
** then continue writing to the database.
*/
SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){
- assert( pPager->dbSize>=nPage );
+ assert( pPager->dbSize>=nPage || CORRUPT_DB );
+ testcase( pPager->dbSize<nPage );
assert( pPager->eState>=PAGER_WRITER_CACHEMOD );
pPager->dbSize = nPage;
@@ -56359,7 +57308,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen(
int rc = SQLITE_OK; /* Return code */
int tempFile = 0; /* True for temp files (incl. in-memory files) */
int memDb = 0; /* True if this is an in-memory file */
-#ifdef SQLITE_ENABLE_DESERIALIZE
+#ifndef SQLITE_OMIT_DESERIALIZE
int memJM = 0; /* Memory journal mode */
#else
# define memJM 0
@@ -56563,7 +57512,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen(
int fout = 0; /* VFS flags returned by xOpen() */
rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout);
assert( !memDb );
-#ifdef SQLITE_ENABLE_DESERIALIZE
+#ifndef SQLITE_OMIT_DESERIALIZE
memJM = (fout&SQLITE_OPEN_MEMORY)!=0;
#endif
readOnly = (fout&SQLITE_OPEN_READONLY)!=0;
@@ -57531,7 +58480,7 @@ SQLITE_PRIVATE int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory
assert( pPager->eState>=PAGER_READER && pPager->eState<PAGER_ERROR );
pPager->subjInMemory = (u8)subjInMemory;
- if( ALWAYS(pPager->eState==PAGER_READER) ){
+ if( pPager->eState==PAGER_READER ){
assert( pPager->pInJournal==0 );
if( pagerUseWal(pPager) ){
@@ -58547,6 +59496,7 @@ static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){
}
aNew[ii].iSubRec = pPager->nSubRec;
aNew[ii].pInSavepoint = sqlite3BitvecCreate(pPager->dbSize);
+ aNew[ii].bTruncateOnRelease = 1;
if( !aNew[ii].pInSavepoint ){
return SQLITE_NOMEM_BKPT;
}
@@ -58628,13 +59578,15 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){
/* If this is a release of the outermost savepoint, truncate
** the sub-journal to zero bytes in size. */
if( op==SAVEPOINT_RELEASE ){
- if( nNew==0 && isOpen(pPager->sjfd) ){
+ PagerSavepoint *pRel = &pPager->aSavepoint[nNew];
+ if( pRel->bTruncateOnRelease && isOpen(pPager->sjfd) ){
/* Only truncate if it is an in-memory sub-journal. */
if( sqlite3JournalIsInMemory(pPager->sjfd) ){
- rc = sqlite3OsTruncate(pPager->sjfd, 0);
+ i64 sz = (pPager->pageSize+4)*pRel->iSubRec;
+ rc = sqlite3OsTruncate(pPager->sjfd, sz);
assert( rc==SQLITE_OK );
}
- pPager->nSubRec = 0;
+ pPager->nSubRec = pRel->iSubRec;
}
}
/* Else this is a rollback operation, playback the specified savepoint.
@@ -60402,7 +61354,6 @@ static void walCleanupHash(Wal *pWal){
int iLimit = 0; /* Zero values greater than this */
int nByte; /* Number of bytes to zero in aPgno[] */
int i; /* Used to iterate through aHash[] */
- int rc; /* Return code form walHashGet() */
assert( pWal->writeLock );
testcase( pWal->hdr.mxFrame==HASHTABLE_NPAGE_ONE-1 );
@@ -60417,8 +61368,8 @@ static void walCleanupHash(Wal *pWal){
*/
assert( pWal->nWiData>walFramePage(pWal->hdr.mxFrame) );
assert( pWal->apWiData[walFramePage(pWal->hdr.mxFrame)] );
- rc = walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &sLoc);
- if( NEVER(rc) ) return; /* Defense-in-depth, in case (1) above is wrong */
+ i = walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &sLoc);
+ if( NEVER(i) ) return; /* Defense-in-depth, in case (1) above is wrong */
/* Zero all hash-table entries that correspond to frame numbers greater
** than pWal->hdr.mxFrame.
@@ -63825,9 +64776,12 @@ struct Btree {
u8 hasIncrblobCur; /* True if there are one or more Incrblob cursors */
int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */
int nBackup; /* Number of backup operations reading this btree */
- u32 iDataVersion; /* Combines with pBt->pPager->iDataVersion */
+ u32 iBDataVersion; /* Combines with pBt->pPager->iDataVersion */
Btree *pNext; /* List of other sharable Btrees from the same db */
Btree *pPrev; /* Back pointer of the same list */
+#ifdef SQLITE_DEBUG
+ u64 nSeek; /* Calls to sqlite3BtreeMovetoUnpacked() */
+#endif
#ifndef SQLITE_OMIT_SHARED_CACHE
BtLock lock; /* Object used to lock page 1 */
#endif
@@ -63839,11 +64793,25 @@ struct Btree {
** If the shared-data extension is enabled, there may be multiple users
** of the Btree structure. At most one of these may open a write transaction,
** but any number may have active read transactions.
+**
+** These values must match SQLITE_TXN_NONE, SQLITE_TXN_READ, and
+** SQLITE_TXN_WRITE
*/
#define TRANS_NONE 0
#define TRANS_READ 1
#define TRANS_WRITE 2
+#if TRANS_NONE!=SQLITE_TXN_NONE
+# error wrong numeric code for no-transaction
+#endif
+#if TRANS_READ!=SQLITE_TXN_READ
+# error wrong numeric code for read-transaction
+#endif
+#if TRANS_WRITE!=SQLITE_TXN_WRITE
+# error wrong numeric code for write-transaction
+#endif
+
+
/*
** An instance of this object represents a single database file.
**
@@ -63913,6 +64881,7 @@ struct BtShared {
Btree *pWriter; /* Btree with currently open write transaction */
#endif
u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */
+ int nPreformatSize; /* Size of last cell written by TransferRow() */
};
/*
@@ -64595,6 +65564,17 @@ SQLITE_API int sqlite3_enable_shared_cache(int enable){
#define hasReadConflicts(a, b) 0
#endif
+#ifdef SQLITE_DEBUG
+/*
+** Return and reset the seek counter for a Btree object.
+*/
+SQLITE_PRIVATE sqlite3_uint64 sqlite3BtreeSeekCount(Btree *pBt){
+ u64 n = pBt->nSeek;
+ pBt->nSeek = 0;
+ return n;
+}
+#endif
+
/*
** Implementation of the SQLITE_CORRUPT_PAGE() macro. Takes a single
** (MemPage*) as an argument. The (MemPage*) must not be NULL.
@@ -65019,7 +65999,7 @@ static void invalidateIncrblobCursors(
int isClearTable /* True if all rows are being deleted */
){
BtCursor *p;
- if( pBtree->hasIncrblobCur==0 ) return;
+ assert( pBtree->hasIncrblobCur );
assert( sqlite3BtreeHoldsMutex(pBtree) );
pBtree->hasIncrblobCur = 0;
for(p=pBtree->pBt->pCursor; p; p=p->pNext){
@@ -65616,6 +66596,24 @@ static SQLITE_NOINLINE void btreeParseCellAdjustSizeForOverflow(
}
/*
+** Given a record with nPayload bytes of payload stored within btree
+** page pPage, return the number of bytes of payload stored locally.
+*/
+static int btreePayloadToLocal(MemPage *pPage, i64 nPayload){
+ int maxLocal; /* Maximum amount of payload held locally */
+ maxLocal = pPage->maxLocal;
+ if( nPayload<=maxLocal ){
+ return nPayload;
+ }else{
+ int minLocal; /* Minimum amount of payload held locally */
+ int surplus; /* Overflow payload available for local storage */
+ minLocal = pPage->minLocal;
+ surplus = minLocal + (nPayload - minLocal)%(pPage->pBt->usableSize-4);
+ return ( surplus <= maxLocal ) ? surplus : minLocal;
+ }
+}
+
+/*
** The following routines are implementations of the MemPage.xParseCell()
** method.
**
@@ -65902,6 +66900,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){
unsigned char *src; /* Source of content */
int iCellFirst; /* First allowable cell index */
int iCellLast; /* Last possible cell index */
+ int iCellStart; /* First cell offset in input */
assert( sqlite3PagerIswriteable(pPage->pDbPage) );
assert( pPage->pBt!=0 );
@@ -65943,7 +66942,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){
if( iFree2+sz2 > usableSize ) return SQLITE_CORRUPT_PAGE(pPage);
memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz));
sz += sz2;
- }else if( NEVER(iFree+sz>usableSize) ){
+ }else if( iFree+sz>usableSize ){
return SQLITE_CORRUPT_PAGE(pPage);
}
@@ -65962,6 +66961,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){
cbrk = usableSize;
iCellLast = usableSize - 4;
+ iCellStart = get2byte(&data[hdr+5]);
for(i=0; i<nCell; i++){
u8 *pAddr; /* The i-th cell pointer */
pAddr = &data[cellOffset + i*2];
@@ -65971,25 +66971,23 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){
/* These conditions have already been verified in btreeInitPage()
** if PRAGMA cell_size_check=ON.
*/
- if( pc<iCellFirst || pc>iCellLast ){
+ if( pc<iCellStart || pc>iCellLast ){
return SQLITE_CORRUPT_PAGE(pPage);
}
- assert( pc>=iCellFirst && pc<=iCellLast );
+ assert( pc>=iCellStart && pc<=iCellLast );
size = pPage->xCellSize(pPage, &src[pc]);
cbrk -= size;
- if( cbrk<iCellFirst || pc+size>usableSize ){
+ if( cbrk<iCellStart || pc+size>usableSize ){
return SQLITE_CORRUPT_PAGE(pPage);
}
- assert( cbrk+size<=usableSize && cbrk>=iCellFirst );
+ assert( cbrk+size<=usableSize && cbrk>=iCellStart );
testcase( cbrk+size==usableSize );
testcase( pc+size==usableSize );
put2byte(pAddr, cbrk);
if( temp==0 ){
- int x;
if( cbrk==pc ) continue;
temp = sqlite3PagerTempSpace(pPage->pBt->pPager);
- x = get2byte(&data[hdr+5]);
- memcpy(&temp[x], &data[x], (cbrk+size) - x);
+ memcpy(&temp[iCellStart], &data[iCellStart], usableSize - iCellStart);
src = temp;
}
memcpy(&data[cbrk], &src[pc], size);
@@ -67088,7 +68086,7 @@ btree_open_out:
** do not change the pager-cache size.
*/
if( sqlite3BtreeSchema(p, 0, 0)==0 ){
- sqlite3PagerSetCachesize(p->pBt->pPager, SQLITE_DEFAULT_CACHE_SIZE);
+ sqlite3BtreeSetCacheSize(p, SQLITE_DEFAULT_CACHE_SIZE);
}
pFile = sqlite3PagerFile(pBt->pPager);
@@ -67191,19 +68189,23 @@ static void freeTempSpace(BtShared *pBt){
*/
SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){
BtShared *pBt = p->pBt;
- BtCursor *pCur;
/* Close all cursors opened via this handle. */
assert( sqlite3_mutex_held(p->db->mutex) );
sqlite3BtreeEnter(p);
- pCur = pBt->pCursor;
- while( pCur ){
- BtCursor *pTmp = pCur;
- pCur = pCur->pNext;
- if( pTmp->pBtree==p ){
- sqlite3BtreeCloseCursor(pTmp);
+
+ /* Verify that no other cursors have this Btree open */
+#ifdef SQLITE_DEBUG
+ {
+ BtCursor *pCur = pBt->pCursor;
+ while( pCur ){
+ BtCursor *pTmp = pCur;
+ pCur = pCur->pNext;
+ assert( pTmp->pBtree!=p );
+
}
}
+#endif
/* Rollback any active transaction and free the handle structure.
** The call to sqlite3BtreeRollback() drops any table-locks held by
@@ -67355,6 +68357,7 @@ SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve,
((pageSize-1)&pageSize)==0 ){
assert( (pageSize & 7)==0 );
assert( !pBt->pCursor );
+ if( nReserve>32 && pageSize==512 ) pageSize = 1024;
pBt->pageSize = (u32)pageSize;
freeTempSpace(pBt);
}
@@ -68584,7 +69587,7 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){
sqlite3BtreeLeave(p);
return rc;
}
- p->iDataVersion--; /* Compensate for pPager->iDataVersion++; */
+ p->iBDataVersion--; /* Compensate for pPager->iDataVersion++; */
pBt->inTransaction = TRANS_READ;
btreeClearHasContent(pBt);
}
@@ -68994,7 +69997,14 @@ SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){
unlockBtreeIfUnused(pBt);
sqlite3_free(pCur->aOverflow);
sqlite3_free(pCur->pKey);
- sqlite3BtreeLeave(pBtree);
+ if( (pBt->openFlags & BTREE_SINGLE) && pBt->pCursor==0 ){
+ /* Since the BtShared is not sharable, there is no need to
+ ** worry about the missing sqlite3BtreeLeave() call here. */
+ assert( pBtree->sharable==0 );
+ sqlite3BtreeClose(pBtree);
+ }else{
+ sqlite3BtreeLeave(pBtree);
+ }
pCur->pBtree = 0;
}
return SQLITE_OK;
@@ -69836,7 +70846,9 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){
for(ii=0; ii<pCur->iPage; ii++){
assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell );
}
- assert( pCur->ix==pCur->pPage->nCell-1 );
+ assert( pCur->ix==pCur->pPage->nCell-1 || CORRUPT_DB );
+ testcase( pCur->ix!=pCur->pPage->nCell-1 );
+ /* ^-- dbsqlfuzz b92b72e4de80b5140c30ab71372ca719b8feb618 */
assert( pCur->pPage->leaf );
#endif
*pRes = 0;
@@ -69942,6 +70954,10 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
}
}
+#ifdef SQLITE_DEBUG
+ pCur->pBtree->nSeek++; /* Performance measurement during testing */
+#endif
+
if( pIdxKey ){
xRecordCompare = sqlite3VdbeFindCompare(pIdxKey);
pIdxKey->errCode = 0;
@@ -70218,7 +71234,7 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){
pPage = pCur->pPage;
idx = ++pCur->ix;
- if( !pPage->isInit ){
+ if( !pPage->isInit || sqlite3FaultSim(412) ){
/* The only known way for this to happen is for there to be a
** recursive SQL function that does a DELETE operation as part of a
** SELECT which deletes content out from under an active cursor
@@ -70599,7 +71615,7 @@ static int allocateBtreePage(
iPage = get4byte(&aData[8+closest*4]);
testcase( iPage==mxPage );
- if( iPage>mxPage ){
+ if( iPage>mxPage || iPage<2 ){
rc = SQLITE_CORRUPT_PGNO(iTrunk);
goto end_allocate_page;
}
@@ -70855,10 +71871,9 @@ static void freePage(MemPage *pPage, int *pRC){
}
/*
-** Free any overflow pages associated with the given Cell. Store
-** size information about the cell in pInfo.
+** Free the overflow pages associated with the given Cell.
*/
-static int clearCell(
+static SQLITE_NOINLINE int clearCellOverflow(
MemPage *pPage, /* The page that contains the Cell */
unsigned char *pCell, /* First byte of the Cell */
CellInfo *pInfo /* Size information about the cell */
@@ -70870,10 +71885,7 @@ static int clearCell(
u32 ovflPageSize;
assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- pPage->xParseCell(pPage, pCell, pInfo);
- if( pInfo->nLocal==pInfo->nPayload ){
- return SQLITE_OK; /* No overflow pages. Return without doing anything */
- }
+ assert( pInfo->nLocal!=pInfo->nPayload );
testcase( pCell + pInfo->nSize == pPage->aDataEnd );
testcase( pCell + (pInfo->nSize-1) == pPage->aDataEnd );
if( pCell + pInfo->nSize > pPage->aDataEnd ){
@@ -70929,6 +71941,21 @@ static int clearCell(
return SQLITE_OK;
}
+/* Call xParseCell to compute the size of a cell. If the cell contains
+** overflow, then invoke cellClearOverflow to clear out that overflow.
+** STore the result code (SQLITE_OK or some error code) in rc.
+**
+** Implemented as macro to force inlining for performance.
+*/
+#define BTREE_CLEAR_CELL(rc, pPage, pCell, sInfo) \
+ pPage->xParseCell(pPage, pCell, &sInfo); \
+ if( sInfo.nLocal!=sInfo.nPayload ){ \
+ rc = clearCellOverflow(pPage, pCell, &sInfo); \
+ }else{ \
+ rc = SQLITE_OK; \
+ }
+
+
/*
** Create the byte sequence used to represent a cell on page pPage
** and write that byte sequence into pCell[]. Overflow pages are
@@ -71451,7 +72478,7 @@ static int rebuildPage(
u8 *pCell = pCArray->apCell[i];
u16 sz = pCArray->szCell[i];
assert( sz>0 );
- if( SQLITE_WITHIN(pCell,aData,pEnd) ){
+ if( SQLITE_WITHIN(pCell,aData+j,pEnd) ){
if( ((uptr)(pCell+sz))>(uptr)pEnd ) return SQLITE_CORRUPT_BKPT;
pCell = &pTmp[pCell - aData];
}else if( (uptr)(pCell+sz)>(uptr)pSrcEnd
@@ -71464,9 +72491,8 @@ static int rebuildPage(
put2byte(pCellptr, (pData - aData));
pCellptr += 2;
if( pData < pCellptr ) return SQLITE_CORRUPT_BKPT;
- memcpy(pData, pCell, sz);
+ memmove(pData, pCell, sz);
assert( sz==pPg->xCellSize(pPg, pCell) || CORRUPT_DB );
- testcase( sz!=pPg->xCellSize(pPg,pCell) )
i++;
if( i>=iEnd ) break;
if( pCArray->ixNx[k]<=i ){
@@ -71605,7 +72631,9 @@ static int pageFreeArray(
}
pFree = pCell;
szFree = sz;
- if( pFree+sz>pEnd ) return 0;
+ if( pFree+sz>pEnd ){
+ return 0;
+ }
}else{
pFree = pCell;
szFree += sz;
@@ -72086,7 +73114,9 @@ static int balance_nonroot(
}
pgno = get4byte(pRight);
while( 1 ){
- rc = getAndInitPage(pBt, pgno, &apOld[i], 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = getAndInitPage(pBt, pgno, &apOld[i], 0, 0);
+ }
if( rc ){
memset(apOld, 0, (i+1)*sizeof(MemPage*));
goto balance_cleanup;
@@ -72125,12 +73155,10 @@ static int balance_nonroot(
if( pBt->btsFlags & BTS_FAST_SECURE ){
int iOff;
+ /* If the following if() condition is not true, the db is corrupted.
+ ** The call to dropCell() below will detect this. */
iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData);
- if( (iOff+szNew[i])>(int)pBt->usableSize ){
- rc = SQLITE_CORRUPT_BKPT;
- memset(apOld, 0, (i+1)*sizeof(MemPage*));
- goto balance_cleanup;
- }else{
+ if( (iOff+szNew[i])<=(int)pBt->usableSize ){
memcpy(&aOvflSpace[iOff], apDiv[i], szNew[i]);
apDiv[i] = &aOvflSpace[apDiv[i]-pParent->aData];
}
@@ -72258,7 +73286,7 @@ static int balance_nonroot(
b.szCell[b.nCell] = b.szCell[b.nCell] - leafCorrection;
if( !pOld->leaf ){
assert( leafCorrection==0 );
- assert( pOld->hdrOffset==0 );
+ assert( pOld->hdrOffset==0 || CORRUPT_DB );
/* The right pointer of the child page pOld becomes the left
** pointer of the divider cell */
memcpy(b.apCell[b.nCell], &pOld->aData[8], 4);
@@ -72424,6 +73452,9 @@ static int balance_nonroot(
apOld[i] = 0;
rc = sqlite3PagerWrite(pNew->pDbPage);
nNew++;
+ if( sqlite3PagerPageRefcount(pNew->pDbPage)!=1+(i==(iParentIdx-nxDiv)) ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }
if( rc ) goto balance_cleanup;
}else{
assert( i>0 );
@@ -72460,7 +73491,7 @@ static int balance_nonroot(
aPgOrder[i] = aPgno[i] = apNew[i]->pgno;
aPgFlags[i] = apNew[i]->pDbPage->flags;
for(j=0; j<i; j++){
- if( aPgno[j]==aPgno[i] ){
+ if( NEVER(aPgno[j]==aPgno[i]) ){
/* This branch is taken if the set of sibling pages somehow contains
** duplicate entries. This can happen if the database is corrupt.
** It would be simpler to detect this as part of the loop below, but
@@ -72578,6 +73609,7 @@ static int balance_nonroot(
u8 *pCell;
u8 *pTemp;
int sz;
+ u8 *pSrcEnd;
MemPage *pNew = apNew[i];
j = cntNew[i];
@@ -72621,6 +73653,12 @@ static int balance_nonroot(
iOvflSpace += sz;
assert( sz<=pBt->maxLocal+23 );
assert( iOvflSpace <= (int)pBt->pageSize );
+ for(k=0; b.ixNx[k]<=i && ALWAYS(k<NB*2); k++){}
+ pSrcEnd = b.apEnd[k];
+ if( SQLITE_WITHIN(pSrcEnd, pCell, pCell+sz) ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto balance_cleanup;
+ }
insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno, &rc);
if( rc!=SQLITE_OK ) goto balance_cleanup;
assert( sqlite3PagerIswriteable(pParent->pDbPage) );
@@ -73128,7 +74166,8 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
unsigned char *oldCell;
unsigned char *newCell = 0;
- assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND))==flags );
+ assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND|BTREE_PREFORMAT))==flags );
+ assert( (flags & BTREE_PREFORMAT)==0 || seekResult || pCur->pKeyInfo==0 );
if( pCur->eState==CURSOR_FAULT ){
assert( pCur->skipNext!=SQLITE_OK );
@@ -73146,7 +74185,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
** keys with no associated data. If the cursor was opened expecting an
** intkey table, the caller should be inserting integer keys with a
** blob of associated data. */
- assert( (pX->pKey==0)==(pCur->pKeyInfo==0) );
+ assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) );
/* Save the positions of any other cursors open on this table.
**
@@ -73162,13 +74201,23 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
if( pCur->curFlags & BTCF_Multiple ){
rc = saveAllCursors(pBt, pCur->pgnoRoot, pCur);
if( rc ) return rc;
+ if( loc && pCur->iPage<0 ){
+ /* This can only happen if the schema is corrupt such that there is more
+ ** than one table or index with the same root page as used by the cursor.
+ ** Which can only happen if the SQLITE_NoSchemaError flag was set when
+ ** the schema was loaded. This cannot be asserted though, as a user might
+ ** set the flag, load the schema, and then unset the flag. */
+ return SQLITE_CORRUPT_BKPT;
+ }
}
if( pCur->pKeyInfo==0 ){
assert( pX->pKey==0 );
/* If this is an insert into a table b-tree, invalidate any incrblob
** cursors open on the row being replaced */
- invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0);
+ if( p->hasIncrblobCur ){
+ invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0);
+ }
/* If BTREE_SAVEPOSITION is set, the cursor must already be pointing
** to a row with the same key as the new entry being inserted.
@@ -73249,17 +74298,16 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
return btreeOverwriteCell(pCur, &x2);
}
}
-
}
assert( pCur->eState==CURSOR_VALID
|| (pCur->eState==CURSOR_INVALID && loc)
|| CORRUPT_DB );
pPage = pCur->pPage;
- assert( pPage->intKey || pX->nKey>=0 );
+ assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) );
assert( pPage->leaf || !pPage->intKey );
if( pPage->nFree<0 ){
- if( pCur->eState>CURSOR_INVALID ){
+ if( NEVER(pCur->eState>CURSOR_INVALID) ){
rc = SQLITE_CORRUPT_BKPT;
}else{
rc = btreeComputeFreeSpace(pPage);
@@ -73273,7 +74321,21 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
assert( pPage->isInit );
newCell = pBt->pTmpSpace;
assert( newCell!=0 );
- rc = fillInCell(pPage, newCell, pX, &szNew);
+ if( flags & BTREE_PREFORMAT ){
+ rc = SQLITE_OK;
+ szNew = pBt->nPreformatSize;
+ if( szNew<4 ) szNew = 4;
+ if( ISAUTOVACUUM && szNew>pPage->maxLocal ){
+ CellInfo info;
+ pPage->xParseCell(pPage, newCell, &info);
+ if( info.nPayload!=info.nLocal ){
+ Pgno ovfl = get4byte(&newCell[szNew-4]);
+ ptrmapPut(pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, &rc);
+ }
+ }
+ }else{
+ rc = fillInCell(pPage, newCell, pX, &szNew);
+ }
if( rc ) goto end_insert;
assert( szNew==pPage->xCellSize(pPage, newCell) );
assert( szNew <= MX_CELL_SIZE(pBt) );
@@ -73289,7 +74351,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
if( !pPage->leaf ){
memcpy(newCell, oldCell, 4);
}
- rc = clearCell(pPage, oldCell, &info);
+ BTREE_CLEAR_CELL(rc, pPage, oldCell, info);
testcase( pCur->curFlags & BTCF_ValidOvfl );
invalidateOverflowCache(pCur);
if( info.nSize==szNew && info.nLocal==info.nPayload
@@ -73381,6 +74443,114 @@ end_insert:
}
/*
+** This function is used as part of copying the current row from cursor
+** pSrc into cursor pDest. If the cursors are open on intkey tables, then
+** parameter iKey is used as the rowid value when the record is copied
+** into pDest. Otherwise, the record is copied verbatim.
+**
+** This function does not actually write the new value to cursor pDest.
+** Instead, it creates and populates any required overflow pages and
+** writes the data for the new cell into the BtShared.pTmpSpace buffer
+** for the destination database. The size of the cell, in bytes, is left
+** in BtShared.nPreformatSize. The caller completes the insertion by
+** calling sqlite3BtreeInsert() with the BTREE_PREFORMAT flag specified.
+**
+** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
+*/
+SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){
+ int rc = SQLITE_OK;
+ BtShared *pBt = pDest->pBt;
+ u8 *aOut = pBt->pTmpSpace; /* Pointer to next output buffer */
+ const u8 *aIn; /* Pointer to next input buffer */
+ u32 nIn; /* Size of input buffer aIn[] */
+ u32 nRem; /* Bytes of data still to copy */
+
+ getCellInfo(pSrc);
+ aOut += putVarint32(aOut, pSrc->info.nPayload);
+ if( pDest->pKeyInfo==0 ) aOut += putVarint(aOut, iKey);
+ nIn = pSrc->info.nLocal;
+ aIn = pSrc->info.pPayload;
+ if( aIn+nIn>pSrc->pPage->aDataEnd ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ nRem = pSrc->info.nPayload;
+ if( nIn==nRem && nIn<pDest->pPage->maxLocal ){
+ memcpy(aOut, aIn, nIn);
+ pBt->nPreformatSize = nIn + (aOut - pBt->pTmpSpace);
+ }else{
+ Pager *pSrcPager = pSrc->pBt->pPager;
+ u8 *pPgnoOut = 0;
+ Pgno ovflIn = 0;
+ DbPage *pPageIn = 0;
+ MemPage *pPageOut = 0;
+ u32 nOut; /* Size of output buffer aOut[] */
+
+ nOut = btreePayloadToLocal(pDest->pPage, pSrc->info.nPayload);
+ pBt->nPreformatSize = nOut + (aOut - pBt->pTmpSpace);
+ if( nOut<pSrc->info.nPayload ){
+ pPgnoOut = &aOut[nOut];
+ pBt->nPreformatSize += 4;
+ }
+
+ if( nRem>nIn ){
+ if( aIn+nIn+4>pSrc->pPage->aDataEnd ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ ovflIn = get4byte(&pSrc->info.pPayload[nIn]);
+ }
+
+ do {
+ nRem -= nOut;
+ do{
+ assert( nOut>0 );
+ if( nIn>0 ){
+ int nCopy = MIN(nOut, nIn);
+ memcpy(aOut, aIn, nCopy);
+ nOut -= nCopy;
+ nIn -= nCopy;
+ aOut += nCopy;
+ aIn += nCopy;
+ }
+ if( nOut>0 ){
+ sqlite3PagerUnref(pPageIn);
+ pPageIn = 0;
+ rc = sqlite3PagerGet(pSrcPager, ovflIn, &pPageIn, PAGER_GET_READONLY);
+ if( rc==SQLITE_OK ){
+ aIn = (const u8*)sqlite3PagerGetData(pPageIn);
+ ovflIn = get4byte(aIn);
+ aIn += 4;
+ nIn = pSrc->pBt->usableSize - 4;
+ }
+ }
+ }while( rc==SQLITE_OK && nOut>0 );
+
+ if( rc==SQLITE_OK && nRem>0 ){
+ Pgno pgnoNew;
+ MemPage *pNew = 0;
+ rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0);
+ put4byte(pPgnoOut, pgnoNew);
+ if( ISAUTOVACUUM && pPageOut ){
+ ptrmapPut(pBt, pgnoNew, PTRMAP_OVERFLOW2, pPageOut->pgno, &rc);
+ }
+ releasePage(pPageOut);
+ pPageOut = pNew;
+ if( pPageOut ){
+ pPgnoOut = pPageOut->aData;
+ put4byte(pPgnoOut, 0);
+ aOut = &pPgnoOut[4];
+ nOut = MIN(pBt->usableSize - 4, nRem);
+ }
+ }
+ }while( nRem>0 && rc==SQLITE_OK );
+
+ releasePage(pPageOut);
+ sqlite3PagerUnref(pPageIn);
+ }
+
+ return rc;
+}
+
+/*
** Delete the entry that the cursor is pointing to.
**
** If the BTREE_SAVEPOSITION bit of the flags parameter is zero, then
@@ -73418,9 +74588,10 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 );
if( pCur->eState==CURSOR_REQUIRESEEK ){
rc = btreeRestoreCursorPosition(pCur);
- if( rc ) return rc;
+ assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID );
+ if( rc || pCur->eState!=CURSOR_VALID ) return rc;
}
- assert( pCur->eState==CURSOR_VALID );
+ assert( CORRUPT_DB || pCur->eState==CURSOR_VALID );
iCellDepth = pCur->iPage;
iCellIdx = pCur->ix;
@@ -73473,7 +74644,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
/* If this is a delete operation to remove a row from a table b-tree,
** invalidate any incrblob cursors open on the row being deleted. */
- if( pCur->pKeyInfo==0 ){
+ if( pCur->pKeyInfo==0 && p->hasIncrblobCur ){
invalidateIncrblobCursors(p, pCur->pgnoRoot, pCur->info.nKey, 0);
}
@@ -73482,7 +74653,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
** itself from within the page. */
rc = sqlite3PagerWrite(pPage->pDbPage);
if( rc ) return rc;
- rc = clearCell(pPage, pCell, &info);
+ BTREE_CLEAR_CELL(rc, pPage, pCell, info);
dropCell(pPage, iCellIdx, info.nSize, &rc);
if( rc ) return rc;
@@ -73769,14 +74940,14 @@ static int clearDatabasePage(
rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange);
if( rc ) goto cleardatabasepage_out;
}
- rc = clearCell(pPage, pCell, &info);
+ BTREE_CLEAR_CELL(rc, pPage, pCell, info);
if( rc ) goto cleardatabasepage_out;
}
if( !pPage->leaf ){
rc = clearDatabasePage(pBt, get4byte(&pPage->aData[hdr+8]), 1, pnChange);
if( rc ) goto cleardatabasepage_out;
- }else if( pnChange ){
- assert( pPage->intKey || CORRUPT_DB );
+ }
+ if( pnChange ){
testcase( !pPage->intKey );
*pnChange += pPage->nCell;
}
@@ -73801,9 +74972,8 @@ cleardatabasepage_out:
** read cursors on the table. Open write cursors are moved to the
** root of the table.
**
-** If pnChange is not NULL, then table iTable must be an intkey table. The
-** integer value pointed to by pnChange is incremented by the number of
-** entries in the table.
+** If pnChange is not NULL, then the integer value pointed to by pnChange
+** is incremented by the number of entries in the table.
*/
SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){
int rc;
@@ -73817,7 +74987,9 @@ SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){
/* Invalidate all incrblob cursors open on table iTable (assuming iTable
** is the root of a table b-tree - if it is not, the following call is
** a no-op). */
- invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1);
+ if( p->hasIncrblobCur ){
+ invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1);
+ }
rc = clearDatabasePage(pBt, (Pgno)iTable, 0, pnChange);
}
sqlite3BtreeLeave(p);
@@ -73977,7 +75149,7 @@ SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){
assert( idx>=0 && idx<=15 );
if( idx==BTREE_DATA_VERSION ){
- *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iDataVersion;
+ *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iBDataVersion;
}else{
*pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]);
}
@@ -74793,11 +75965,12 @@ SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *p){
}
/*
-** Return non-zero if a transaction is active.
+** Return one of SQLITE_TXN_NONE, SQLITE_TXN_READ, or SQLITE_TXN_WRITE
+** to describe the current transaction state of Btree p.
*/
-SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree *p){
+SQLITE_PRIVATE int sqlite3BtreeTxnState(Btree *p){
assert( p==0 || sqlite3_mutex_held(p->db->mutex) );
- return (p && (p->inTrans==TRANS_WRITE));
+ return p ? p->inTrans : 0;
}
#ifndef SQLITE_OMIT_WAL
@@ -74826,14 +75999,8 @@ SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree *p, int eMode, int *pnLog, int *
#endif
/*
-** Return non-zero if a read (or write) transaction is active.
+** Return true if there is currently a backup running on Btree p.
*/
-SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree *p){
- assert( p );
- assert( sqlite3_mutex_held(p->db->mutex) );
- return p->inTrans!=TRANS_NONE;
-}
-
SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree *p){
assert( p );
assert( sqlite3_mutex_held(p->db->mutex) );
@@ -75179,7 +76346,7 @@ static int setDestPgsz(sqlite3_backup *p){
** message in database handle db.
*/
static int checkReadTransaction(sqlite3 *db, Btree *p){
- if( sqlite3BtreeIsInReadTrans(p) ){
+ if( sqlite3BtreeTxnState(p)!=SQLITE_TXN_NONE ){
sqlite3ErrorWithMsg(db, SQLITE_ERROR, "destination database is in use");
return SQLITE_ERROR;
}
@@ -75410,7 +76577,7 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
** one now. If a transaction is opened here, then it will be closed
** before this function exits.
*/
- if( rc==SQLITE_OK && 0==sqlite3BtreeIsInReadTrans(p->pSrc) ){
+ if( rc==SQLITE_OK && SQLITE_TXN_NONE==sqlite3BtreeTxnState(p->pSrc) ){
rc = sqlite3BtreeBeginTrans(p->pSrc, 0, 0);
bCloseTrans = 1;
}
@@ -75782,7 +76949,7 @@ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
sqlite3BtreeEnter(pTo);
sqlite3BtreeEnter(pFrom);
- assert( sqlite3BtreeIsInTrans(pTo) );
+ assert( sqlite3BtreeTxnState(pTo)==SQLITE_TXN_WRITE );
pFd = sqlite3PagerFile(sqlite3BtreePager(pTo));
if( pFd->pMethods ){
i64 nByte = sqlite3BtreeGetPageSize(pFrom)*(i64)sqlite3BtreeLastPage(pFrom);
@@ -75818,7 +76985,7 @@ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
sqlite3PagerClearCache(sqlite3BtreePager(b.pDest));
}
- assert( sqlite3BtreeIsInTrans(pTo)==0 );
+ assert( sqlite3BtreeTxnState(pTo)!=SQLITE_TXN_WRITE );
copy_finished:
sqlite3BtreeLeave(pFrom);
sqlite3BtreeLeave(pTo);
@@ -75905,7 +77072,9 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){
/* The szMalloc field holds the correct memory allocation size */
assert( p->szMalloc==0
- || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc) );
+ || (p->flags==MEM_Undefined
+ && p->szMalloc<=sqlite3DbMallocSize(p->db,p->zMalloc))
+ || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc));
/* If p holds a string or blob, the Mem.z must point to exactly
** one of the following:
@@ -76069,7 +77238,9 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPre
testcase( bPreserve && pMem->z==0 );
assert( pMem->szMalloc==0
- || pMem->szMalloc==sqlite3DbMallocSize(pMem->db, pMem->zMalloc) );
+ || (pMem->flags==MEM_Undefined
+ && pMem->szMalloc<=sqlite3DbMallocSize(pMem->db,pMem->zMalloc))
+ || pMem->szMalloc==sqlite3DbMallocSize(pMem->db,pMem->zMalloc));
if( pMem->szMalloc>0 && bPreserve && pMem->z==pMem->zMalloc ){
if( pMem->db ){
pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n);
@@ -76898,11 +78069,11 @@ SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){
SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
Mem *pMem, /* Memory cell to set to string value */
const char *z, /* String pointer */
- int n, /* Bytes in string, or negative */
+ i64 n, /* Bytes in string, or negative */
u8 enc, /* Encoding of z. 0 for BLOBs */
void (*xDel)(void*) /* Destructor function */
){
- int nByte = n; /* New value for pMem->n */
+ i64 nByte = n; /* New value for pMem->n */
int iLimit; /* Maximum allowed string or blob size */
u16 flags = 0; /* New value for pMem->flags */
@@ -76924,7 +78095,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
if( nByte<0 ){
assert( enc!=0 );
if( enc==SQLITE_UTF8 ){
- nByte = 0x7fffffff & (int)strlen(z);
+ nByte = strlen(z);
}else{
for(nByte=0; nByte<=iLimit && (z[nByte] | z[nByte+1]); nByte+=2){}
}
@@ -76936,7 +78107,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
** management (one of MEM_Dyn or MEM_Static).
*/
if( xDel==SQLITE_TRANSIENT ){
- u32 nAlloc = nByte;
+ i64 nAlloc = nByte;
if( flags&MEM_Term ){
nAlloc += (enc==SQLITE_UTF8?1:2);
}
@@ -76962,7 +78133,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
}
}
- pMem->n = nByte;
+ pMem->n = (int)(nByte & 0x7fffffff);
pMem->flags = flags;
if( enc ){
pMem->enc = enc;
@@ -76982,7 +78153,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
#endif
if( nByte>iLimit ){
- return SQLITE_TOOBIG;
+ return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG);
}
return SQLITE_OK;
@@ -77773,7 +78944,7 @@ SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){
p->pNext = db->pVdbe;
p->pPrev = 0;
db->pVdbe = p;
- p->magic = VDBE_MAGIC_INIT;
+ p->iVdbeMagic = VDBE_MAGIC_INIT;
p->pParse = pParse;
pParse->pVdbe = p;
assert( pParse->aLabel==0 );
@@ -77974,7 +79145,7 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){
VdbeOp *pOp;
i = p->nOp;
- assert( p->magic==VDBE_MAGIC_INIT );
+ assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
assert( op>=0 && op<0xff );
if( p->nOpAlloc<=i ){
return growOp3(p, op, p1, p2, p3);
@@ -78209,10 +79380,12 @@ SQLITE_PRIVATE void sqlite3VdbeExplainPop(Parse *pParse){
** The zWhere string must have been obtained from sqlite3_malloc().
** This routine will take ownership of the allocated memory.
*/
-SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere){
+SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere, u16 p5){
int j;
sqlite3VdbeAddOp4(p, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC);
+ sqlite3VdbeChangeP5(p, p5);
for(j=0; j<p->db->nDb; j++) sqlite3VdbeUsesBtree(p, j);
+ sqlite3MayAbort(p->pParse);
}
/*
@@ -78302,7 +79475,7 @@ static SQLITE_NOINLINE void resizeResolveLabel(Parse *p, Vdbe *v, int j){
SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){
Parse *p = v->pParse;
int j = ADDR(x);
- assert( v->magic==VDBE_MAGIC_INIT );
+ assert( v->iVdbeMagic==VDBE_MAGIC_INIT );
assert( j<-p->nLabel );
assert( j>=0 );
#ifdef SQLITE_DEBUG
@@ -78441,7 +79614,7 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename
|| opcode==OP_VDestroy
|| opcode==OP_VCreate
- || (opcode==OP_ParseSchema && pOp->p4.z==0)
+ || opcode==OP_ParseSchema
|| ((opcode==OP_Halt || opcode==OP_HaltIfNull)
&& ((pOp->p1)!=SQLITE_OK && pOp->p2==OE_Abort))
){
@@ -78627,7 +79800,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
** Return the address of the next instruction to be inserted.
*/
SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe *p){
- assert( p->magic==VDBE_MAGIC_INIT );
+ assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
return p->nOp;
}
@@ -78712,7 +79885,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(
int i;
VdbeOp *pOut, *pFirst;
assert( nOp>0 );
- assert( p->magic==VDBE_MAGIC_INIT );
+ assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
if( p->nOp + nOp > p->nOpAlloc && growOpArray(p, nOp) ){
return 0;
}
@@ -79036,7 +80209,7 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int
sqlite3 *db;
assert( p!=0 );
db = p->db;
- assert( p->magic==VDBE_MAGIC_INIT );
+ assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
assert( p->aOp!=0 || db->mallocFailed );
if( db->mallocFailed ){
if( n!=P4_VTAB ) freeP4(db, n, (void*)*(char**)&zP4);
@@ -79165,7 +80338,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){
/* C89 specifies that the constant "dummy" will be initialized to all
** zeros, which is correct. MSVC generates a warning, nevertheless. */
static VdbeOp dummy; /* Ignore the MSVC warning about no initializer */
- assert( p->magic==VDBE_MAGIC_INIT );
+ assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
if( addr<0 ){
addr = p->nOp - 1;
}
@@ -79223,11 +80396,7 @@ SQLITE_PRIVATE char *sqlite3VdbeDisplayComment(
char c;
zSynopsis = zOpName += nOpName + 1;
if( strncmp(zSynopsis,"IF ",3)==0 ){
- if( pOp->p5 & SQLITE_STOREP2 ){
- sqlite3_snprintf(sizeof(zAlt), zAlt, "r[P2] = (%s)", zSynopsis+3);
- }else{
- sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3);
- }
+ sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3);
zSynopsis = zAlt;
}
for(ii=0; (c = zSynopsis[ii])!=0; ii++){
@@ -79259,7 +80428,7 @@ SQLITE_PRIVATE char *sqlite3VdbeDisplayComment(
sqlite3_str_appendf(&x, "%d", v1);
}else if( pCtx->argc>1 ){
sqlite3_str_appendf(&x, "%d..%d", v1, v1+pCtx->argc-1);
- }else{
+ }else if( x.accError==0 ){
assert( x.nChar>2 );
x.nChar -= 2;
ii++;
@@ -79850,7 +81019,7 @@ SQLITE_PRIVATE int sqlite3VdbeList(
Op *pOp; /* Current opcode */
assert( p->explain );
- assert( p->magic==VDBE_MAGIC_RUN );
+ assert( p->iVdbeMagic==VDBE_MAGIC_RUN );
assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM );
/* Even though this opcode does not use dynamic strings for
@@ -80030,14 +81199,14 @@ SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){
int i;
#endif
assert( p!=0 );
- assert( p->magic==VDBE_MAGIC_INIT || p->magic==VDBE_MAGIC_RESET );
+ assert( p->iVdbeMagic==VDBE_MAGIC_INIT || p->iVdbeMagic==VDBE_MAGIC_RESET );
/* There should be at least one opcode.
*/
assert( p->nOp>0 );
/* Set the magic to VDBE_MAGIC_RUN sooner rather than later. */
- p->magic = VDBE_MAGIC_RUN;
+ p->iVdbeMagic = VDBE_MAGIC_RUN;
#ifdef SQLITE_DEBUG
for(i=0; i<p->nMem; i++){
@@ -80093,8 +81262,10 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
assert( p!=0 );
assert( p->nOp>0 );
assert( pParse!=0 );
- assert( p->magic==VDBE_MAGIC_INIT );
+ assert( p->iVdbeMagic==VDBE_MAGIC_INIT );
assert( pParse==p->pParse );
+ p->pVList = pParse->pVList;
+ pParse->pVList = 0;
db = p->db;
assert( db->mallocFailed==0 );
nVar = pParse->nVar;
@@ -80179,8 +81350,6 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
}
}
- p->pVList = pParse->pVList;
- pParse->pVList = 0;
if( db->mallocFailed ){
p->nVar = 0;
p->nCursor = 0;
@@ -80208,20 +81377,15 @@ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){
return;
}
assert( pCx->pBtx==0 || pCx->eCurType==CURTYPE_BTREE );
+ assert( pCx->pBtx==0 || pCx->isEphemeral );
switch( pCx->eCurType ){
case CURTYPE_SORTER: {
sqlite3VdbeSorterClose(p->db, pCx);
break;
}
case CURTYPE_BTREE: {
- if( pCx->isEphemeral ){
- if( pCx->pBtx ) sqlite3BtreeClose(pCx->pBtx);
- /* The pCx->pCursor will be close automatically, if it exists, by
- ** the call above. */
- }else{
- assert( pCx->uc.pCursor!=0 );
- sqlite3BtreeCloseCursor(pCx->uc.pCursor);
- }
+ assert( pCx->uc.pCursor!=0 );
+ sqlite3BtreeCloseCursor(pCx->uc.pCursor);
break;
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
@@ -80401,7 +81565,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
*/
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
- if( sqlite3BtreeIsInTrans(pBt) ){
+ if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){
/* Whether or not a database might need a super-journal depends upon
** its journal mode (among other things). This matrix determines which
** journal modes use a super-journal and which do not */
@@ -80536,7 +81700,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
*/
for(i=0; i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
- if( sqlite3BtreeIsInTrans(pBt) ){
+ if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){
char const *zFile = sqlite3BtreeGetJournalname(pBt);
if( zFile==0 ){
continue; /* Ignore TEMP and :memory: databases */
@@ -80778,7 +81942,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
** one, or the complete transaction if there is no statement transaction.
*/
- if( p->magic!=VDBE_MAGIC_RUN ){
+ if( p->iVdbeMagic!=VDBE_MAGIC_RUN ){
return SQLITE_OK;
}
if( db->mallocFailed ){
@@ -80936,7 +82100,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
assert( db->nVdbeRead>=db->nVdbeWrite );
assert( db->nVdbeWrite>=0 );
}
- p->magic = VDBE_MAGIC_HALT;
+ p->iVdbeMagic = VDBE_MAGIC_HALT;
checkActiveVdbeCnt(db);
if( db->mallocFailed ){
p->rc = SQLITE_NOMEM_BKPT;
@@ -81109,7 +82273,7 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
}
}
#endif
- p->magic = VDBE_MAGIC_RESET;
+ p->iVdbeMagic = VDBE_MAGIC_RESET;
return p->rc & db->errMask;
}
@@ -81119,7 +82283,7 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
*/
SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe *p){
int rc = SQLITE_OK;
- if( p->magic==VDBE_MAGIC_RUN || p->magic==VDBE_MAGIC_HALT ){
+ if( p->iVdbeMagic==VDBE_MAGIC_RUN || p->iVdbeMagic==VDBE_MAGIC_HALT ){
rc = sqlite3VdbeReset(p);
assert( (rc & p->db->errMask)==rc );
}
@@ -81180,7 +82344,7 @@ SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){
vdbeFreeOpArray(db, pSub->aOp, pSub->nOp);
sqlite3DbFree(db, pSub);
}
- if( p->magic!=VDBE_MAGIC_INIT ){
+ if( p->iVdbeMagic!=VDBE_MAGIC_INIT ){
releaseMemArray(p->aVar, p->nVar);
sqlite3DbFree(db, p->pVList);
sqlite3DbFree(db, p->pFree);
@@ -81228,7 +82392,7 @@ SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){
if( p->pNext ){
p->pNext->pPrev = p->pPrev;
}
- p->magic = VDBE_MAGIC_DEAD;
+ p->iVdbeMagic = VDBE_MAGIC_DEAD;
p->db = 0;
sqlite3DbFreeNN(db, p);
}
@@ -81305,6 +82469,7 @@ SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, u32 *piCol){
assert( p->eCurType==CURTYPE_BTREE || p->eCurType==CURTYPE_PSEUDO );
if( p->deferredMoveto ){
u32 iMap;
+ assert( !p->isEphemeral );
if( p->aAltMap && (iMap = p->aAltMap[1+*piCol])>0 && !p->nullRow ){
*pp = p->pAltCursor;
*piCol = iMap - 1;
@@ -82032,9 +83197,12 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem
static int sqlite3IntFloatCompare(i64 i, double r){
if( sizeof(LONGDOUBLE_TYPE)>8 ){
LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i;
+ testcase( x<r );
+ testcase( x>r );
+ testcase( x==r );
if( x<r ) return -1;
- if( x>r ) return +1;
- return 0;
+ if( x>r ) return +1; /*NO_TEST*/ /* work around bugs in gcov */
+ return 0; /*NO_TEST*/ /* work around bugs in gcov */
}else{
i64 y;
double s;
@@ -82930,7 +84098,8 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
const char *zDb, /* Database name */
Table *pTab, /* Modified table */
i64 iKey1, /* Initial key value */
- int iReg /* Register for new.* record */
+ int iReg, /* Register for new.* record */
+ int iBlobWrite
){
sqlite3 *db = v->db;
i64 iKey2;
@@ -82966,6 +84135,7 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
preupdate.iKey1 = iKey1;
preupdate.iKey2 = iKey2;
preupdate.pTab = pTab;
+ preupdate.iBlobWrite = iBlobWrite;
db->pPreUpdate = &preupdate;
db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2);
@@ -83379,7 +84549,7 @@ static int invokeValueDestructor(
}else{
xDel((void*)p);
}
- if( pCtx ) sqlite3_result_error_toobig(pCtx);
+ sqlite3_result_error_toobig(pCtx);
return SQLITE_TOOBIG;
}
SQLITE_API void sqlite3_result_blob(
@@ -83604,7 +84774,7 @@ static int sqlite3Step(Vdbe *p){
int rc;
assert(p);
- if( p->magic!=VDBE_MAGIC_RUN ){
+ if( p->iVdbeMagic!=VDBE_MAGIC_RUN ){
/* We used to require that sqlite3_reset() be called before retrying
** sqlite3_step() after any error or after SQLITE_DONE. But beginning
** with version 3.7.0, we changed this so that sqlite3_reset() would
@@ -84320,7 +85490,7 @@ static int vdbeUnbind(Vdbe *p, int i){
return SQLITE_MISUSE_BKPT;
}
sqlite3_mutex_enter(p->db->mutex);
- if( p->magic!=VDBE_MAGIC_RUN || p->pc>=0 ){
+ if( p->iVdbeMagic!=VDBE_MAGIC_RUN || p->pc>=0 ){
sqlite3Error(p->db, SQLITE_MISUSE);
sqlite3_mutex_leave(p->db->mutex);
sqlite3_log(SQLITE_MISUSE,
@@ -84361,7 +85531,7 @@ static int bindText(
sqlite3_stmt *pStmt, /* The statement to bind against */
int i, /* Index of the parameter to bind */
const void *zData, /* Pointer to the data to be bound */
- int nData, /* Number of bytes of data to be bound */
+ i64 nData, /* Number of bytes of data to be bound */
void (*xDel)(void*), /* Destructor for the data */
u8 encoding /* Encoding for the data */
){
@@ -84413,11 +85583,7 @@ SQLITE_API int sqlite3_bind_blob64(
void (*xDel)(void*)
){
assert( xDel!=SQLITE_DYNAMIC );
- if( nData>0x7fffffff ){
- return invokeValueDestructor(zData, xDel, 0);
- }else{
- return bindText(pStmt, i, zData, (int)nData, xDel, 0);
- }
+ return bindText(pStmt, i, zData, nData, xDel, 0);
}
SQLITE_API int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){
int rc;
@@ -84487,12 +85653,8 @@ SQLITE_API int sqlite3_bind_text64(
unsigned char enc
){
assert( xDel!=SQLITE_DYNAMIC );
- if( nData>0x7fffffff ){
- return invokeValueDestructor(zData, xDel, 0);
- }else{
- if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE;
- return bindText(pStmt, i, zData, (int)nData, xDel, enc);
- }
+ if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE;
+ return bindText(pStmt, i, zData, nData, xDel, enc);
}
#ifndef SQLITE_OMIT_UTF16
SQLITE_API int sqlite3_bind_text16(
@@ -84674,7 +85836,7 @@ SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){
*/
SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){
Vdbe *v = (Vdbe*)pStmt;
- return v!=0 && v->magic==VDBE_MAGIC_RUN && v->pc>=0;
+ return v!=0 && v->iVdbeMagic==VDBE_MAGIC_RUN && v->pc>=0;
}
/*
@@ -84894,6 +86056,17 @@ SQLITE_API int sqlite3_preupdate_depth(sqlite3 *db){
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
/*
+** This function is designed to be called from within a pre-update callback
+** only.
+*/
+SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *db){
+ PreUpdate *p = db->pPreUpdate;
+ return (p ? p->iBlobWrite : -1);
+}
+#endif
+
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+/*
** This function is called from within a pre-update callback to retrieve
** a field of the row currently being updated or inserted.
*/
@@ -85166,7 +86339,7 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(
assert( idx>0 );
}
zRawSql += nToken;
- nextIndex = idx + 1;
+ nextIndex = MAX(idx + 1, nextIndex);
assert( idx>0 && idx<=p->nVar );
pVar = &p->aVar[idx-1];
if( pVar->flags & MEM_Null ){
@@ -85510,26 +86683,39 @@ static VdbeCursor *allocateCursor(
assert( iCur>=0 && iCur<p->nCursor );
if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/
- /* Before calling sqlite3VdbeFreeCursor(), ensure the isEphemeral flag
- ** is clear. Otherwise, if this is an ephemeral cursor created by
- ** OP_OpenDup, the cursor will not be closed and will still be part
- ** of a BtShared.pCursor list. */
- if( p->apCsr[iCur]->pBtx==0 ) p->apCsr[iCur]->isEphemeral = 0;
sqlite3VdbeFreeCursor(p, p->apCsr[iCur]);
p->apCsr[iCur] = 0;
}
- if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){
- p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z;
- memset(pCx, 0, offsetof(VdbeCursor,pAltCursor));
- pCx->eCurType = eCurType;
- pCx->iDb = iDb;
- pCx->nField = nField;
- pCx->aOffset = &pCx->aType[nField];
- if( eCurType==CURTYPE_BTREE ){
- pCx->uc.pCursor = (BtCursor*)
- &pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField];
- sqlite3BtreeCursorZero(pCx->uc.pCursor);
+
+ /* There used to be a call to sqlite3VdbeMemClearAndResize() to make sure
+ ** the pMem used to hold space for the cursor has enough storage available
+ ** in pMem->zMalloc. But for the special case of the aMem[] entries used
+ ** to hold cursors, it is faster to in-line the logic. */
+ assert( pMem->flags==MEM_Undefined );
+ assert( (pMem->flags & MEM_Dyn)==0 );
+ assert( pMem->szMalloc==0 || pMem->z==pMem->zMalloc );
+ if( pMem->szMalloc<nByte ){
+ if( pMem->szMalloc>0 ){
+ sqlite3DbFreeNN(pMem->db, pMem->zMalloc);
+ }
+ pMem->z = pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, nByte);
+ if( pMem->zMalloc==0 ){
+ pMem->szMalloc = 0;
+ return 0;
}
+ pMem->szMalloc = nByte;
+ }
+
+ p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->zMalloc;
+ memset(pCx, 0, offsetof(VdbeCursor,pAltCursor));
+ pCx->eCurType = eCurType;
+ pCx->iDb = iDb;
+ pCx->nField = nField;
+ pCx->aOffset = &pCx->aType[nField];
+ if( eCurType==CURTYPE_BTREE ){
+ pCx->uc.pCursor = (BtCursor*)
+ &pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField];
+ sqlite3BtreeCursorZero(pCx->uc.pCursor);
}
return pCx;
}
@@ -85676,7 +86862,10 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){
sqlite3_int64 ix;
assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 );
assert( (pMem->flags & (MEM_Str|MEM_Blob))!=0 );
- ExpandBlob(pMem);
+ if( ExpandBlob(pMem) ){
+ pMem->u.i = 0;
+ return MEM_Int;
+ }
rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc);
if( rc<=0 ){
if( rc==0 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1 ){
@@ -85814,6 +87003,11 @@ static void registerTrace(int iReg, Mem *p){
printf("\n");
sqlite3VdbeCheckMemInvariants(p);
}
+/**/ void sqlite3PrintMem(Mem *pMem){
+ memTracePrint(pMem);
+ printf("\n");
+ fflush(stdout);
+}
#endif
#ifdef SQLITE_DEBUG
@@ -86012,7 +87206,7 @@ SQLITE_PRIVATE int sqlite3VdbeExec(
#endif
/*** INSERT STACK UNION HERE ***/
- assert( p->magic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */
+ assert( p->iVdbeMagic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */
sqlite3VdbeEnter(p);
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
if( db->xProgress ){
@@ -86772,6 +87966,26 @@ case OP_IntCopy: { /* out2 */
break;
}
+/* Opcode: ChngCntRow P1 P2 * * *
+** Synopsis: output=r[P1]
+**
+** Output value in register P1 as the chance count for a DML statement,
+** due to the "PRAGMA count_changes=ON" setting. Or, if there was a
+** foreign key error in the statement, trigger the error now.
+**
+** This opcode is a variant of OP_ResultRow that checks the foreign key
+** immediate constraint count and throws an error if the count is
+** non-zero. The P2 opcode must be 1.
+*/
+case OP_ChngCntRow: {
+ assert( pOp->p2==1 );
+ if( (rc = sqlite3VdbeCheckFk(p,0))!=SQLITE_OK ){
+ goto abort_due_to_error;
+ }
+ /* Fall through to the next case, OP_ResultRow */
+ /* no break */ deliberate_fall_through
+}
+
/* Opcode: ResultRow P1 P2 * * *
** Synopsis: output=r[P1@P2]
**
@@ -86785,37 +87999,9 @@ case OP_ResultRow: {
Mem *pMem;
int i;
assert( p->nResColumn==pOp->p2 );
- assert( pOp->p1>0 );
+ assert( pOp->p1>0 || CORRUPT_DB );
assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 );
- /* If this statement has violated immediate foreign key constraints, do
- ** not return the number of rows modified. And do not RELEASE the statement
- ** transaction. It needs to be rolled back. */
- if( SQLITE_OK!=(rc = sqlite3VdbeCheckFk(p, 0)) ){
- assert( db->flags&SQLITE_CountRows );
- assert( p->usesStmtJournal );
- goto abort_due_to_error;
- }
-
- /* If the SQLITE_CountRows flag is set in sqlite3.flags mask, then
- ** DML statements invoke this opcode to return the number of rows
- ** modified to the user. This is the only way that a VM that
- ** opens a statement transaction may invoke this opcode.
- **
- ** In case this is such a statement, close any statement transaction
- ** opened by this VM before returning control to the user. This is to
- ** ensure that statement-transactions are always nested, not overlapping.
- ** If the open statement-transaction is not closed here, then the user
- ** may step another VM that opens its own statement transaction. This
- ** may lead to overlapping statement transactions.
- **
- ** The statement transaction is never a top-level transaction. Hence
- ** the RELEASE call below can never fail.
- */
- assert( p->iStatement==0 || db->flags&SQLITE_CountRows );
- rc = sqlite3VdbeCloseStatement(p, SAVEPOINT_RELEASE);
- assert( rc==SQLITE_OK );
-
/* Invalidate all ephemeral cursor row caches */
p->cacheCtr = (p->cacheCtr + 2)|1;
@@ -87255,8 +88441,7 @@ case OP_Cast: { /* in1 */
** Synopsis: IF r[P3]==r[P1]
**
** Compare the values in register P1 and P3. If reg(P3)==reg(P1) then
-** jump to address P2. Or if the SQLITE_STOREP2 flag is set in P5, then
-** store the result of comparison in register P2.
+** jump to address P2.
**
** The SQLITE_AFF_MASK portion of P5 must be an affinity character -
** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made
@@ -87282,9 +88467,8 @@ case OP_Cast: { /* in1 */
** If neither operand is NULL the result is the same as it would be if
** the SQLITE_NULLEQ flag were omitted from P5.
**
-** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the
-** content of r[P2] is only changed if the new value is NULL or 0 (false).
-** In other words, a prior r[P2] value will not be overwritten by 1 (true).
+** This opcode saves the result of comparison for use by the new
+** OP_Jump opcode.
*/
/* Opcode: Ne P1 P2 P3 P4 P5
** Synopsis: IF r[P3]!=r[P1]
@@ -87292,17 +88476,12 @@ case OP_Cast: { /* in1 */
** This works just like the Eq opcode except that the jump is taken if
** the operands in registers P1 and P3 are not equal. See the Eq opcode for
** additional information.
-**
-** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the
-** content of r[P2] is only changed if the new value is NULL or 1 (true).
-** In other words, a prior r[P2] value will not be overwritten by 0 (false).
*/
/* Opcode: Lt P1 P2 P3 P4 P5
** Synopsis: IF r[P3]<r[P1]
**
** Compare the values in register P1 and P3. If reg(P3)<reg(P1) then
-** jump to address P2. Or if the SQLITE_STOREP2 flag is set in P5 store
-** the result of comparison (0 or 1 or NULL) into register P2.
+** jump to address P2.
**
** If the SQLITE_JUMPIFNULL bit of P5 is set and either reg(P1) or
** reg(P3) is NULL then the take the jump. If the SQLITE_JUMPIFNULL
@@ -87325,6 +88504,9 @@ case OP_Cast: { /* in1 */
** numeric, then a numeric comparison is used. If the two values
** are of different types, then numbers are considered less than
** strings and strings are considered less than blobs.
+**
+** This opcode saves the result of comparison for use by the new
+** OP_Jump opcode.
*/
/* Opcode: Le P1 P2 P3 P4 P5
** Synopsis: IF r[P3]<=r[P1]
@@ -87362,6 +88544,31 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
pIn3 = &aMem[pOp->p3];
flags1 = pIn1->flags;
flags3 = pIn3->flags;
+ if( (flags1 & flags3 & MEM_Int)!=0 ){
+ assert( (pOp->p5 & SQLITE_AFF_MASK)!=SQLITE_AFF_TEXT || CORRUPT_DB );
+ /* Common case of comparison of two integers */
+ if( pIn3->u.i > pIn1->u.i ){
+ iCompare = +1;
+ if( sqlite3aGTb[pOp->opcode] ){
+ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3);
+ goto jump_to_p2;
+ }
+ }else if( pIn3->u.i < pIn1->u.i ){
+ iCompare = -1;
+ if( sqlite3aLTb[pOp->opcode] ){
+ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3);
+ goto jump_to_p2;
+ }
+ }else{
+ iCompare = 0;
+ if( sqlite3aEQb[pOp->opcode] ){
+ VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3);
+ goto jump_to_p2;
+ }
+ }
+ VdbeBranchTaken(0, (pOp->p5 & SQLITE_NULLEQ)?2:3);
+ break;
+ }
if( (flags1 | flags3)&MEM_Null ){
/* One or both operands are NULL */
if( pOp->p5 & SQLITE_NULLEQ ){
@@ -87384,22 +88591,16 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
** then the result is always NULL.
** The jump is taken if the SQLITE_JUMPIFNULL bit is set.
*/
- if( pOp->p5 & SQLITE_STOREP2 ){
- pOut = &aMem[pOp->p2];
- iCompare = 1; /* Operands are not equal */
- memAboutToChange(p, pOut);
- MemSetTypeFlag(pOut, MEM_Null);
- REGISTER_TRACE(pOp->p2, pOut);
- }else{
- VdbeBranchTaken(2,3);
- if( pOp->p5 & SQLITE_JUMPIFNULL ){
- goto jump_to_p2;
- }
+ iCompare = 1; /* Operands are not equal */
+ VdbeBranchTaken(2,3);
+ if( pOp->p5 & SQLITE_JUMPIFNULL ){
+ goto jump_to_p2;
}
break;
}
}else{
- /* Neither operand is NULL. Do a comparison. */
+ /* Neither operand is NULL and we couldn't do the special high-speed
+ ** integer comparison case. So do a general-case comparison. */
affinity = pOp->p5 & SQLITE_AFF_MASK;
if( affinity>=SQLITE_AFF_NUMERIC ){
if( (flags1 | flags3)&MEM_Str ){
@@ -87412,14 +88613,6 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
applyNumericAffinity(pIn3,0);
}
}
- /* Handle the common case of integer comparison here, as an
- ** optimization, to avoid a call to sqlite3MemCompare() */
- if( (pIn1->flags & pIn3->flags & MEM_Int)!=0 ){
- if( pIn3->u.i > pIn1->u.i ){ res = +1; goto compare_op; }
- if( pIn3->u.i < pIn1->u.i ){ res = -1; goto compare_op; }
- res = 0;
- goto compare_op;
- }
}else if( affinity==SQLITE_AFF_TEXT ){
if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
testcase( pIn1->flags & MEM_Int );
@@ -87442,7 +88635,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 );
res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl);
}
-compare_op:
+
/* At this point, res is negative, zero, or positive if reg[P1] is
** less than, equal to, or greater than reg[P3], respectively. Compute
** the answer to this operator in res2, depending on what the comparison
@@ -87451,16 +88644,14 @@ compare_op:
** order: NE, EQ, GT, LE, LT, GE */
assert( OP_Eq==OP_Ne+1 ); assert( OP_Gt==OP_Ne+2 ); assert( OP_Le==OP_Ne+3 );
assert( OP_Lt==OP_Ne+4 ); assert( OP_Ge==OP_Ne+5 );
- if( res<0 ){ /* ne, eq, gt, le, lt, ge */
- static const unsigned char aLTb[] = { 1, 0, 0, 1, 1, 0 };
- res2 = aLTb[pOp->opcode - OP_Ne];
+ if( res<0 ){
+ res2 = sqlite3aLTb[pOp->opcode];
}else if( res==0 ){
- static const unsigned char aEQb[] = { 0, 1, 0, 1, 0, 1 };
- res2 = aEQb[pOp->opcode - OP_Ne];
+ res2 = sqlite3aEQb[pOp->opcode];
}else{
- static const unsigned char aGTb[] = { 1, 0, 1, 0, 0, 1 };
- res2 = aGTb[pOp->opcode - OP_Ne];
+ res2 = sqlite3aGTb[pOp->opcode];
}
+ iCompare = res;
/* Undo any changes made by applyAffinity() to the input registers. */
assert( (pIn3->flags & MEM_Dyn) == (flags3 & MEM_Dyn) );
@@ -87468,67 +88659,39 @@ compare_op:
assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) );
pIn1->flags = flags1;
- if( pOp->p5 & SQLITE_STOREP2 ){
- pOut = &aMem[pOp->p2];
- iCompare = res;
- if( (pOp->p5 & SQLITE_KEEPNULL)!=0 ){
- /* The KEEPNULL flag prevents OP_Eq from overwriting a NULL with 1
- ** and prevents OP_Ne from overwriting NULL with 0. This flag
- ** is only used in contexts where either:
- ** (1) op==OP_Eq && (r[P2]==NULL || r[P2]==0)
- ** (2) op==OP_Ne && (r[P2]==NULL || r[P2]==1)
- ** Therefore it is not necessary to check the content of r[P2] for
- ** NULL. */
- assert( pOp->opcode==OP_Ne || pOp->opcode==OP_Eq );
- assert( res2==0 || res2==1 );
- testcase( res2==0 && pOp->opcode==OP_Eq );
- testcase( res2==1 && pOp->opcode==OP_Eq );
- testcase( res2==0 && pOp->opcode==OP_Ne );
- testcase( res2==1 && pOp->opcode==OP_Ne );
- if( (pOp->opcode==OP_Eq)==res2 ) break;
- }
- memAboutToChange(p, pOut);
- MemSetTypeFlag(pOut, MEM_Int);
- pOut->u.i = res2;
- REGISTER_TRACE(pOp->p2, pOut);
- }else{
- VdbeBranchTaken(res2!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3);
- if( res2 ){
- goto jump_to_p2;
- }
+ VdbeBranchTaken(res2!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3);
+ if( res2 ){
+ goto jump_to_p2;
}
break;
}
-/* Opcode: ElseNotEq * P2 * * *
+/* Opcode: ElseEq * P2 * * *
**
** This opcode must follow an OP_Lt or OP_Gt comparison operator. There
** can be zero or more OP_ReleaseReg opcodes intervening, but no other
** opcodes are allowed to occur between this instruction and the previous
-** OP_Lt or OP_Gt. Furthermore, the prior OP_Lt or OP_Gt must have the
-** SQLITE_STOREP2 bit set in the P5 field.
+** OP_Lt or OP_Gt.
**
** If result of an OP_Eq comparison on the same two operands as the
-** prior OP_Lt or OP_Gt would have been NULL or false (0), then then
-** jump to P2. If the result of an OP_Eq comparison on the two previous
-** operands would have been true (1), then fall through.
+** prior OP_Lt or OP_Gt would have been true, then jump to P2.
+** If the result of an OP_Eq comparison on the two previous
+** operands would have been false or NULL, then fall through.
*/
-case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */
+case OP_ElseEq: { /* same as TK_ESCAPE, jump */
#ifdef SQLITE_DEBUG
/* Verify the preconditions of this opcode - that it follows an OP_Lt or
- ** OP_Gt with the SQLITE_STOREP2 flag set, with zero or more intervening
- ** OP_ReleaseReg opcodes */
+ ** OP_Gt with zero or more intervening OP_ReleaseReg opcodes */
int iAddr;
for(iAddr = (int)(pOp - aOp) - 1; ALWAYS(iAddr>=0); iAddr--){
if( aOp[iAddr].opcode==OP_ReleaseReg ) continue;
assert( aOp[iAddr].opcode==OP_Lt || aOp[iAddr].opcode==OP_Gt );
- assert( aOp[iAddr].p5 & SQLITE_STOREP2 );
break;
}
#endif /* SQLITE_DEBUG */
- VdbeBranchTaken(iCompare!=0, 2);
- if( iCompare!=0 ) goto jump_to_p2;
+ VdbeBranchTaken(iCompare==0, 2);
+ if( iCompare==0 ) goto jump_to_p2;
break;
}
@@ -87839,6 +89002,24 @@ case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */
break;
}
+/* Opcode: ZeroOrNull P1 P2 P3 * *
+** Synopsis: r[P2] = 0 OR NULL
+**
+** If all both registers P1 and P3 are NOT NULL, then store a zero in
+** register P2. If either registers P1 or P3 are NULL then put
+** a NULL in register P2.
+*/
+case OP_ZeroOrNull: { /* in1, in2, out2, in3 */
+ if( (aMem[pOp->p1].flags & MEM_Null)!=0
+ || (aMem[pOp->p3].flags & MEM_Null)!=0
+ ){
+ sqlite3VdbeMemSetNull(aMem + pOp->p2);
+ }else{
+ sqlite3VdbeMemSetInt64(aMem + pOp->p2, 0);
+ }
+ break;
+}
+
/* Opcode: NotNull P1 P2 * * *
** Synopsis: if r[P1]!=NULL goto P2
**
@@ -88814,7 +89995,8 @@ case OP_AutoCommit: {
** active.
** If P2 is non-zero, then a write-transaction is started, or if a
** read-transaction is already active, it is upgraded to a write-transaction.
-** If P2 is zero, then a read-transaction is started.
+** If P2 is zero, then a read-transaction is started. If P2 is 2 or more
+** then an exclusive transaction is started.
**
** P1 is the index of the database file on which the transaction is
** started. Index 0 is the main database file and index 1 is the
@@ -88848,6 +90030,7 @@ case OP_Transaction: {
assert( p->bIsReader );
assert( p->readOnly==0 || pOp->p2==0 );
+ assert( pOp->p2>=0 && pOp->p2<=2 );
assert( pOp->p1>=0 && pOp->p1<db->nDb );
assert( DbMaskTest(p->btreeMask, pOp->p1) );
if( pOp->p2 && (db->flags & SQLITE_QueryOnly)!=0 ){
@@ -88873,7 +90056,7 @@ case OP_Transaction: {
&& pOp->p2
&& (db->autoCommit==0 || db->nVdbeRead>1)
){
- assert( sqlite3BtreeIsInTrans(pBt) );
+ assert( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE );
if( p->iStatement==0 ){
assert( db->nStatement>=0 && db->nSavepoint>=0 );
db->nStatement++;
@@ -89206,7 +90389,7 @@ case OP_OpenDup: {
pOrig = p->apCsr[pOp->p2];
assert( pOrig );
- assert( pOrig->pBtx!=0 ); /* Only ephemeral cursors can be duplicated */
+ assert( pOrig->isEphemeral ); /* Only ephemeral cursors can be duplicated */
pCx = allocateCursor(p, pOp->p1, pOrig->nField, -1, CURTYPE_BTREE);
if( pCx==0 ) goto no_mem;
@@ -89216,7 +90399,10 @@ case OP_OpenDup: {
pCx->isTable = pOrig->isTable;
pCx->pgnoRoot = pOrig->pgnoRoot;
pCx->isOrdered = pOrig->isOrdered;
- rc = sqlite3BtreeCursor(pOrig->pBtx, pCx->pgnoRoot, BTREE_WRCSR,
+ pCx->pBtx = pOrig->pBtx;
+ pCx->hasBeenDuped = 1;
+ pOrig->hasBeenDuped = 1;
+ rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR,
pCx->pKeyInfo, pCx->uc.pCursor);
/* The sqlite3BtreeCursor() routine can only fail for the first cursor
** opened for a database. Since there is already an open cursor when this
@@ -89226,7 +90412,7 @@ case OP_OpenDup: {
}
-/* Opcode: OpenEphemeral P1 P2 * P4 P5
+/* Opcode: OpenEphemeral P1 P2 P3 P4 P5
** Synopsis: nColumn=P2
**
** Open a new cursor P1 to a transient table.
@@ -89246,6 +90432,10 @@ case OP_OpenDup: {
** in btree.h. These flags control aspects of the operation of
** the btree. The BTREE_OMIT_JOURNAL and BTREE_SINGLE flags are
** added automatically.
+**
+** If P3 is positive, then reg[P3] is modified slightly so that it
+** can be used as zero-length data for OP_Insert. This is an optimization
+** that avoids an extra OP_Blob opcode to initialize that register.
*/
/* Opcode: OpenAutoindex P1 P2 * P4 *
** Synopsis: nColumn=P2
@@ -89268,10 +90458,20 @@ case OP_OpenEphemeral: {
SQLITE_OPEN_TRANSIENT_DB;
assert( pOp->p1>=0 );
assert( pOp->p2>=0 );
+ if( pOp->p3>0 ){
+ /* Make register reg[P3] into a value that can be used as the data
+ ** form sqlite3BtreeInsert() where the length of the data is zero. */
+ assert( pOp->p2==0 ); /* Only used when number of columns is zero */
+ assert( pOp->opcode==OP_OpenEphemeral );
+ assert( aMem[pOp->p3].flags & MEM_Null );
+ aMem[pOp->p3].n = 0;
+ aMem[pOp->p3].z = "";
+ }
pCx = p->apCsr[pOp->p1];
- if( pCx && pCx->pBtx ){
- /* If the ephermeral table is already open, erase all existing content
- ** so that the table is empty again, rather than creating a new table. */
+ if( pCx && !pCx->hasBeenDuped ){
+ /* If the ephermeral table is already open and has no duplicates from
+ ** OP_OpenDup, then erase all existing content so that the table is
+ ** empty again, rather than creating a new table. */
assert( pCx->isEphemeral );
pCx->seqCount = 0;
pCx->cacheStatus = CACHE_STALE;
@@ -89285,33 +90485,36 @@ case OP_OpenEphemeral: {
vfsFlags);
if( rc==SQLITE_OK ){
rc = sqlite3BtreeBeginTrans(pCx->pBtx, 1, 0);
- }
- if( rc==SQLITE_OK ){
- /* If a transient index is required, create it by calling
- ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before
- ** opening it. If a transient table is required, just use the
- ** automatically created table with root-page 1 (an BLOB_INTKEY table).
- */
- if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){
- assert( pOp->p4type==P4_KEYINFO );
- rc = sqlite3BtreeCreateTable(pCx->pBtx, &pCx->pgnoRoot,
- BTREE_BLOBKEY | pOp->p5);
- if( rc==SQLITE_OK ){
- assert( pCx->pgnoRoot==SCHEMA_ROOT+1 );
- assert( pKeyInfo->db==db );
- assert( pKeyInfo->enc==ENC(db) );
- rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR,
- pKeyInfo, pCx->uc.pCursor);
+ if( rc==SQLITE_OK ){
+ /* If a transient index is required, create it by calling
+ ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before
+ ** opening it. If a transient table is required, just use the
+ ** automatically created table with root-page 1 (an BLOB_INTKEY table).
+ */
+ if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){
+ assert( pOp->p4type==P4_KEYINFO );
+ rc = sqlite3BtreeCreateTable(pCx->pBtx, &pCx->pgnoRoot,
+ BTREE_BLOBKEY | pOp->p5);
+ if( rc==SQLITE_OK ){
+ assert( pCx->pgnoRoot==SCHEMA_ROOT+1 );
+ assert( pKeyInfo->db==db );
+ assert( pKeyInfo->enc==ENC(db) );
+ rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR,
+ pKeyInfo, pCx->uc.pCursor);
+ }
+ pCx->isTable = 0;
+ }else{
+ pCx->pgnoRoot = SCHEMA_ROOT;
+ rc = sqlite3BtreeCursor(pCx->pBtx, SCHEMA_ROOT, BTREE_WRCSR,
+ 0, pCx->uc.pCursor);
+ pCx->isTable = 1;
}
- pCx->isTable = 0;
- }else{
- pCx->pgnoRoot = SCHEMA_ROOT;
- rc = sqlite3BtreeCursor(pCx->pBtx, SCHEMA_ROOT, BTREE_WRCSR,
- 0, pCx->uc.pCursor);
- pCx->isTable = 1;
+ }
+ pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED);
+ if( rc ){
+ sqlite3BtreeClose(pCx->pBtx);
}
}
- pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED);
}
if( rc ) goto abort_due_to_error;
pCx->nullRow = 1;
@@ -89710,22 +90913,183 @@ seek_not_found:
break;
}
-/* Opcode: SeekHit P1 P2 * * *
-** Synopsis: seekHit=P2
+
+/* Opcode: SeekScan P1 P2 * * *
+** Synopsis: Scan-ahead up to P1 rows
+**
+** This opcode is a prefix opcode to OP_SeekGE. In other words, this
+** opcode must be immediately followed by OP_SeekGE. This constraint is
+** checked by assert() statements.
+**
+** This opcode uses the P1 through P4 operands of the subsequent
+** OP_SeekGE. In the text that follows, the operands of the subsequent
+** OP_SeekGE opcode are denoted as SeekOP.P1 through SeekOP.P4. Only
+** the P1 and P2 operands of this opcode are also used, and are called
+** This.P1 and This.P2.
+**
+** This opcode helps to optimize IN operators on a multi-column index
+** where the IN operator is on the later terms of the index by avoiding
+** unnecessary seeks on the btree, substituting steps to the next row
+** of the b-tree instead. A correct answer is obtained if this opcode
+** is omitted or is a no-op.
+**
+** The SeekGE.P3 and SeekGE.P4 operands identify an unpacked key which
+** is the desired entry that we want the cursor SeekGE.P1 to be pointing
+** to. Call this SeekGE.P4/P5 row the "target".
+**
+** If the SeekGE.P1 cursor is not currently pointing to a valid row,
+** then this opcode is a no-op and control passes through into the OP_SeekGE.
+**
+** If the SeekGE.P1 cursor is pointing to a valid row, then that row
+** might be the target row, or it might be near and slightly before the
+** target row. This opcode attempts to position the cursor on the target
+** row by, perhaps by invoking sqlite3BtreeStep() on the cursor
+** between 0 and This.P1 times.
+**
+** There are three possible outcomes from this opcode:<ol>
+**
+** <li> If after This.P1 steps, the cursor is still pointing to a place that
+** is earlier in the btree than the target row, then fall through
+** into the subsquence OP_SeekGE opcode.
+**
+** <li> If the cursor is successfully moved to the target row by 0 or more
+** sqlite3BtreeNext() calls, then jump to This.P2, which will land just
+** past the OP_IdxGT or OP_IdxGE opcode that follows the OP_SeekGE.
+**
+** <li> If the cursor ends up past the target row (indicating the the target
+** row does not exist in the btree) then jump to SeekOP.P2.
+** </ol>
+*/
+case OP_SeekScan: {
+ VdbeCursor *pC;
+ int res;
+ int nStep;
+ UnpackedRecord r;
+
+ assert( pOp[1].opcode==OP_SeekGE );
+
+ /* pOp->p2 points to the first instruction past the OP_IdxGT that
+ ** follows the OP_SeekGE. */
+ assert( pOp->p2>=(int)(pOp-aOp)+2 );
+ assert( aOp[pOp->p2-1].opcode==OP_IdxGT || aOp[pOp->p2-1].opcode==OP_IdxGE );
+ testcase( aOp[pOp->p2-1].opcode==OP_IdxGE );
+ assert( pOp[1].p1==aOp[pOp->p2-1].p1 );
+ assert( pOp[1].p2==aOp[pOp->p2-1].p2 );
+ assert( pOp[1].p3==aOp[pOp->p2-1].p3 );
+
+ assert( pOp->p1>0 );
+ pC = p->apCsr[pOp[1].p1];
+ assert( pC!=0 );
+ assert( pC->eCurType==CURTYPE_BTREE );
+ assert( !pC->isTable );
+ if( !sqlite3BtreeCursorIsValidNN(pC->uc.pCursor) ){
+#ifdef SQLITE_DEBUG
+ if( db->flags&SQLITE_VdbeTrace ){
+ printf("... cursor not valid - fall through\n");
+ }
+#endif
+ break;
+ }
+ nStep = pOp->p1;
+ assert( nStep>=1 );
+ r.pKeyInfo = pC->pKeyInfo;
+ r.nField = (u16)pOp[1].p4.i;
+ r.default_rc = 0;
+ r.aMem = &aMem[pOp[1].p3];
+#ifdef SQLITE_DEBUG
+ {
+ int i;
+ for(i=0; i<r.nField; i++){
+ assert( memIsValid(&r.aMem[i]) );
+ REGISTER_TRACE(pOp[1].p3+i, &aMem[pOp[1].p3+i]);
+ }
+ }
+#endif
+ res = 0; /* Not needed. Only used to silence a warning. */
+ while(1){
+ rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res);
+ if( rc ) goto abort_due_to_error;
+ if( res>0 ){
+ seekscan_search_fail:
+#ifdef SQLITE_DEBUG
+ if( db->flags&SQLITE_VdbeTrace ){
+ printf("... %d steps and then skip\n", pOp->p1 - nStep);
+ }
+#endif
+ VdbeBranchTaken(1,3);
+ pOp++;
+ goto jump_to_p2;
+ }
+ if( res==0 ){
+#ifdef SQLITE_DEBUG
+ if( db->flags&SQLITE_VdbeTrace ){
+ printf("... %d steps and then success\n", pOp->p1 - nStep);
+ }
+#endif
+ VdbeBranchTaken(2,3);
+ goto jump_to_p2;
+ break;
+ }
+ if( nStep<=0 ){
+#ifdef SQLITE_DEBUG
+ if( db->flags&SQLITE_VdbeTrace ){
+ printf("... fall through after %d steps\n", pOp->p1);
+ }
+#endif
+ VdbeBranchTaken(0,3);
+ break;
+ }
+ nStep--;
+ rc = sqlite3BtreeNext(pC->uc.pCursor, 0);
+ if( rc ){
+ if( rc==SQLITE_DONE ){
+ rc = SQLITE_OK;
+ goto seekscan_search_fail;
+ }else{
+ goto abort_due_to_error;
+ }
+ }
+ }
+
+ break;
+}
+
+
+/* Opcode: SeekHit P1 P2 P3 * *
+** Synopsis: set P2<=seekHit<=P3
**
-** Set the seekHit flag on cursor P1 to the value in P2.
-** The seekHit flag is used by the IfNoHope opcode.
+** Increase or decrease the seekHit value for cursor P1, if necessary,
+** so that it is no less than P2 and no greater than P3.
**
-** P1 must be a valid b-tree cursor. P2 must be a boolean value,
-** either 0 or 1.
+** The seekHit integer represents the maximum of terms in an index for which
+** there is known to be at least one match. If the seekHit value is smaller
+** than the total number of equality terms in an index lookup, then the
+** OP_IfNoHope opcode might run to see if the IN loop can be abandoned
+** early, thus saving work. This is part of the IN-early-out optimization.
+**
+** P1 must be a valid b-tree cursor.
*/
case OP_SeekHit: {
VdbeCursor *pC;
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
- assert( pOp->p2==0 || pOp->p2==1 );
- pC->seekHit = pOp->p2 & 1;
+ assert( pOp->p3>=pOp->p2 );
+ if( pC->seekHit<pOp->p2 ){
+#ifdef SQLITE_DEBUG
+ if( db->flags&SQLITE_VdbeTrace ){
+ printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p2);
+ }
+#endif
+ pC->seekHit = pOp->p2;
+ }else if( pC->seekHit>pOp->p3 ){
+#ifdef SQLITE_DEBUG
+ if( db->flags&SQLITE_VdbeTrace ){
+ printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p3);
+ }
+#endif
+ pC->seekHit = pOp->p3;
+ }
break;
}
@@ -89783,16 +91147,20 @@ case OP_IfNotOpen: { /* jump */
** Synopsis: key=r[P3@P4]
**
** Register P3 is the first of P4 registers that form an unpacked
-** record.
+** record. Cursor P1 is an index btree. P2 is a jump destination.
+** In other words, the operands to this opcode are the same as the
+** operands to OP_NotFound and OP_IdxGT.
**
-** Cursor P1 is on an index btree. If the seekHit flag is set on P1, then
-** this opcode is a no-op. But if the seekHit flag of P1 is clear, then
-** check to see if there is any entry in P1 that matches the
-** prefix identified by P3 and P4. If no entry matches the prefix,
-** jump to P2. Otherwise fall through.
+** This opcode is an optimization attempt only. If this opcode always
+** falls through, the correct answer is still obtained, but extra works
+** is performed.
**
-** This opcode behaves like OP_NotFound if the seekHit
-** flag is clear and it behaves like OP_Noop if the seekHit flag is set.
+** A value of N in the seekHit flag of cursor P1 means that there exists
+** a key P3:N that will match some record in the index. We want to know
+** if it is possible for a record P3:P4 to match some record in the
+** index. If it is not possible, we can skips some work. So if seekHit
+** is less than P4, attempt to find out if a match is possible by running
+** OP_NotFound.
**
** This opcode is used in IN clause processing for a multi-column key.
** If an IN clause is attached to an element of the key other than the
@@ -89834,7 +91202,12 @@ case OP_IfNoHope: { /* jump, in3 */
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
- if( pC->seekHit ) break;
+#ifdef SQLITE_DEBUG
+ if( db->flags&SQLITE_VdbeTrace ){
+ printf("seekHit is %d\n", pC->seekHit);
+ }
+#endif
+ if( pC->seekHit>=pOp->p4.i ) break;
/* Fall through into OP_NotFound */
/* no break */ deliberate_fall_through
}
@@ -89916,6 +91289,7 @@ case OP_Found: { /* jump, in3 */
}else{
VdbeBranchTaken(takeJump||alreadyExists==0,2);
if( takeJump || !alreadyExists ) goto jump_to_p2;
+ if( pOp->opcode==OP_IfNoHope ) pC->seekHit = pOp->p4.i;
}
break;
}
@@ -90066,8 +91440,10 @@ case OP_NewRowid: { /* out2 */
VdbeCursor *pC; /* Cursor of table to get the new rowid */
int res; /* Result of an sqlite3BtreeLast() */
int cnt; /* Counter to limit the number of searches */
+#ifndef SQLITE_OMIT_AUTOINCREMENT
Mem *pMem; /* Register holding largest rowid for AUTOINCREMENT */
VdbeFrame *pFrame; /* Root frame of VDBE */
+#endif
v = 0;
res = 0;
@@ -90260,7 +91636,7 @@ case OP_Insert: {
/* Invoke the pre-update hook, if any */
if( pTab ){
if( db->xPreUpdateCallback && !(pOp->p5 & OPFLAG_ISUPDATE) ){
- sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey,pOp->p2);
+ sqlite3VdbePreUpdateHook(p,pC,SQLITE_INSERT,zDb,pTab,x.nKey,pOp->p2,-1);
}
if( db->xUpdateCallback==0 || pTab->aCol==0 ){
/* Prevent post-update hook from running in cases when it should not */
@@ -90272,7 +91648,7 @@ case OP_Insert: {
if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++;
if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey;
- assert( pData->flags & (MEM_Blob|MEM_Str) );
+ assert( (pData->flags & (MEM_Blob|MEM_Str))!=0 || pData->n==0 );
x.pData = pData->z;
x.nData = pData->n;
seekResult = ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0);
@@ -90283,7 +91659,8 @@ case OP_Insert: {
}
x.pKey = 0;
rc = sqlite3BtreeInsert(pC->uc.pCursor, &x,
- (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION)), seekResult
+ (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)),
+ seekResult
);
pC->deferredMoveto = 0;
pC->cacheStatus = CACHE_STALE;
@@ -90300,6 +91677,33 @@ case OP_Insert: {
break;
}
+/* Opcode: RowCell P1 P2 P3 * *
+**
+** P1 and P2 are both open cursors. Both must be opened on the same type
+** of table - intkey or index. This opcode is used as part of copying
+** the current row from P2 into P1. If the cursors are opened on intkey
+** tables, register P3 contains the rowid to use with the new record in
+** P1. If they are opened on index tables, P3 is not used.
+**
+** This opcode must be followed by either an Insert or InsertIdx opcode
+** with the OPFLAG_PREFORMAT flag set to complete the insert operation.
+*/
+case OP_RowCell: {
+ VdbeCursor *pDest; /* Cursor to write to */
+ VdbeCursor *pSrc; /* Cursor to read from */
+ i64 iKey; /* Rowid value to insert with */
+ assert( pOp[1].opcode==OP_Insert || pOp[1].opcode==OP_IdxInsert );
+ assert( pOp[1].opcode==OP_Insert || pOp->p3==0 );
+ assert( pOp[1].opcode==OP_IdxInsert || pOp->p3>0 );
+ assert( pOp[1].p5 & OPFLAG_PREFORMAT );
+ pDest = p->apCsr[pOp->p1];
+ pSrc = p->apCsr[pOp->p2];
+ iKey = pOp->p3 ? aMem[pOp->p3].u.i : 0;
+ rc = sqlite3BtreeTransferRow(pDest->uc.pCursor, pSrc->uc.pCursor, iKey);
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ break;
+};
+
/* Opcode: Delete P1 P2 P3 P4 P5
**
** Delete the record at which the P1 cursor is currently pointing.
@@ -90392,7 +91796,7 @@ case OP_Delete: {
sqlite3VdbePreUpdateHook(p, pC,
(opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE,
zDb, pTab, pC->movetoTarget,
- pOp->p3
+ pOp->p3, -1
);
}
if( opflags & OPFLAG_ISNOOP ) break;
@@ -90955,7 +92359,7 @@ case OP_IdxInsert: { /* in2 */
assert( pC!=0 );
assert( !isSorter(pC) );
pIn2 = &aMem[pOp->p2];
- assert( pIn2->flags & MEM_Blob );
+ assert( (pIn2->flags & MEM_Blob) || (pOp->p5 & OPFLAG_PREFORMAT) );
if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++;
assert( pC->eCurType==CURTYPE_BTREE );
assert( pC->isTable==0 );
@@ -90966,7 +92370,7 @@ case OP_IdxInsert: { /* in2 */
x.aMem = aMem + pOp->p3;
x.nMem = (u16)pOp->p4.i;
rc = sqlite3BtreeInsert(pC->uc.pCursor, &x,
- (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION)),
+ (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)),
((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0)
);
assert( pC->deferredMoveto==0 );
@@ -91039,7 +92443,7 @@ case OP_IdxDelete: {
rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE);
if( rc ) goto abort_due_to_error;
}else if( pOp->p5 ){
- rc = SQLITE_CORRUPT_INDEX;
+ rc = sqlite3ReportError(SQLITE_CORRUPT_INDEX, __LINE__, "index corruption");
goto abort_due_to_error;
}
assert( pC->deferredMoveto==0 );
@@ -91118,6 +92522,8 @@ case OP_IdxRowid: { /* out2 */
pTabCur->deferredMoveto = 1;
assert( pOp->p4type==P4_INTARRAY || pOp->p4.ai==0 );
pTabCur->aAltMap = pOp->p4.ai;
+ assert( !pC->isEphemeral );
+ assert( !pTabCur->isEphemeral );
pTabCur->pAltCursor = pC;
}else{
pOut = out2Prerelease(p, pOp);
@@ -91148,7 +92554,7 @@ case OP_FinishSeek: {
break;
}
-/* Opcode: IdxGE P1 P2 P3 P4 P5
+/* Opcode: IdxGE P1 P2 P3 P4 *
** Synopsis: key=r[P3@P4]
**
** The P4 register values beginning with P3 form an unpacked index
@@ -91159,7 +92565,7 @@ case OP_FinishSeek: {
** If the P1 index entry is greater than or equal to the key value
** then jump to P2. Otherwise fall through to the next instruction.
*/
-/* Opcode: IdxGT P1 P2 P3 P4 P5
+/* Opcode: IdxGT P1 P2 P3 P4 *
** Synopsis: key=r[P3@P4]
**
** The P4 register values beginning with P3 form an unpacked index
@@ -91170,7 +92576,7 @@ case OP_FinishSeek: {
** If the P1 index entry is greater than the key value
** then jump to P2. Otherwise fall through to the next instruction.
*/
-/* Opcode: IdxLT P1 P2 P3 P4 P5
+/* Opcode: IdxLT P1 P2 P3 P4 *
** Synopsis: key=r[P3@P4]
**
** The P4 register values beginning with P3 form an unpacked index
@@ -91181,7 +92587,7 @@ case OP_FinishSeek: {
** If the P1 index entry is less than the key value then jump to P2.
** Otherwise fall through to the next instruction.
*/
-/* Opcode: IdxLE P1 P2 P3 P4 P5
+/* Opcode: IdxLE P1 P2 P3 P4 *
** Synopsis: key=r[P3@P4]
**
** The P4 register values beginning with P3 form an unpacked index
@@ -91207,7 +92613,6 @@ case OP_IdxGE: { /* jump */
assert( pC->eCurType==CURTYPE_BTREE );
assert( pC->uc.pCursor!=0);
assert( pC->deferredMoveto==0 );
- assert( pOp->p5==0 || pOp->p5==1 );
assert( pOp->p4type==P4_INT32 );
r.pKeyInfo = pC->pKeyInfo;
r.nField = (u16)pOp->p4.i;
@@ -91228,8 +92633,31 @@ case OP_IdxGE: { /* jump */
}
}
#endif
- res = 0; /* Not needed. Only used to silence a warning. */
- rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res);
+
+ /* Inlined version of sqlite3VdbeIdxKeyCompare() */
+ {
+ i64 nCellKey = 0;
+ BtCursor *pCur;
+ Mem m;
+
+ assert( pC->eCurType==CURTYPE_BTREE );
+ pCur = pC->uc.pCursor;
+ assert( sqlite3BtreeCursorIsValid(pCur) );
+ nCellKey = sqlite3BtreePayloadSize(pCur);
+ /* nCellKey will always be between 0 and 0xffffffff because of the way
+ ** that btreeParseCellPtr() and sqlite3GetVarint32() are implemented */
+ if( nCellKey<=0 || nCellKey>0x7fffffff ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto abort_due_to_error;
+ }
+ sqlite3VdbeMemInit(&m, db, 0);
+ rc = sqlite3VdbeMemFromBtreeZeroOffset(pCur, (u32)nCellKey, &m);
+ if( rc ) goto abort_due_to_error;
+ res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, &r, 0);
+ sqlite3VdbeMemRelease(&m);
+ }
+ /* End of inlined sqlite3VdbeIdxKeyCompare() */
+
assert( (OP_IdxLE&1)==(OP_IdxLT&1) && (OP_IdxGE&1)==(OP_IdxGT&1) );
if( (pOp->opcode&1)==(OP_IdxLT&1) ){
assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxLT );
@@ -91239,7 +92667,7 @@ case OP_IdxGE: { /* jump */
res++;
}
VdbeBranchTaken(res>0,2);
- if( rc ) goto abort_due_to_error;
+ assert( rc==SQLITE_OK );
if( res>0 ) goto jump_to_p2;
break;
}
@@ -91314,11 +92742,10 @@ case OP_Destroy: { /* out2 */
** P2==1 then the table to be clear is in the auxiliary database file
** that is used to store tables create using CREATE TEMPORARY TABLE.
**
-** If the P3 value is non-zero, then the table referred to must be an
-** intkey table (an SQL table, not an index). In this case the row change
-** count is incremented by the number of rows in the table being cleared.
-** If P3 is greater than zero, then the value stored in register P3 is
-** also incremented by the number of rows in the table being cleared.
+** If the P3 value is non-zero, then the row change count is incremented
+** by the number of rows in the table being cleared. If P3 is greater
+** than zero, then the value stored in register P3 is also incremented
+** by the number of rows in the table being cleared.
**
** See also: Destroy
*/
@@ -91329,9 +92756,7 @@ case OP_Clear: {
nChange = 0;
assert( p->readOnly==0 );
assert( DbMaskTest(p->btreeMask, pOp->p2) );
- rc = sqlite3BtreeClearTable(
- db->aDb[pOp->p2].pBt, (u32)pOp->p1, (pOp->p3 ? &nChange : 0)
- );
+ rc = sqlite3BtreeClearTable(db->aDb[pOp->p2].pBt, (u32)pOp->p1, &nChange);
if( pOp->p3 ){
p->nChange += nChange;
if( pOp->p3>0 ){
@@ -91437,13 +92862,15 @@ case OP_ParseSchema: {
iDb = pOp->p1;
assert( iDb>=0 && iDb<db->nDb );
- assert( DbHasProperty(db, iDb, DB_SchemaLoaded) );
+ assert( DbHasProperty(db, iDb, DB_SchemaLoaded)
+ || db->mallocFailed
+ || (CORRUPT_DB && (db->flags & SQLITE_NoSchemaError)!=0) );
#ifndef SQLITE_OMIT_ALTERTABLE
if( pOp->p4.z==0 ){
sqlite3SchemaClear(db->aDb[iDb].pSchema);
db->mDbFlags &= ~DBFLAG_SchemaKnownOk;
- rc = sqlite3InitOne(db, iDb, &p->zErrMsg, INITFLAG_AlterTable);
+ rc = sqlite3InitOne(db, iDb, &p->zErrMsg, pOp->p5);
db->mDbFlags |= DBFLAG_SchemaChange;
p->expired = 0;
}else
@@ -92319,6 +93746,7 @@ case OP_JournalMode: { /* out2 */
pPager = sqlite3BtreePager(pBt);
eOld = sqlite3PagerGetJournalMode(pPager);
if( eNew==PAGER_JOURNALMODE_QUERY ) eNew = eOld;
+ assert( sqlite3BtreeHoldsMutex(pBt) );
if( !sqlite3PagerOkToChangeJournalMode(pPager) ) eNew = eOld;
#ifndef SQLITE_OMIT_WAL
@@ -92365,7 +93793,7 @@ case OP_JournalMode: { /* out2 */
/* Open a transaction on the database file. Regardless of the journal
** mode, this transaction always uses a rollback journal.
*/
- assert( sqlite3BtreeIsInTrans(pBt)==0 );
+ assert( sqlite3BtreeTxnState(pBt)!=SQLITE_TXN_WRITE );
if( rc==SQLITE_OK ){
rc = sqlite3BtreeSetVersion(pBt, (eNew==PAGER_JOURNALMODE_WAL ? 2 : 1));
}
@@ -93305,7 +94733,11 @@ default: { /* This is really OP_Noop, OP_Explain */
** an error of some kind.
*/
abort_due_to_error:
- if( db->mallocFailed ) rc = SQLITE_NOMEM_BKPT;
+ if( db->mallocFailed ){
+ rc = SQLITE_NOMEM_BKPT;
+ }else if( rc==SQLITE_IOERR_CORRUPTFS ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }
assert( rc );
if( p->zErrMsg==0 && rc!=SQLITE_IOERR_NOMEM ){
sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc));
@@ -93793,7 +95225,7 @@ static int blobReadWrite(
sqlite3_int64 iKey;
iKey = sqlite3BtreeIntegerKey(p->pCsr);
sqlite3VdbePreUpdateHook(
- v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1
+ v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol
);
}
#endif
@@ -93864,6 +95296,7 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){
rc = SQLITE_ABORT;
}else{
char *zErr;
+ ((Vdbe*)p->pStmt)->rc = SQLITE_OK;
rc = blobSeekToRow(p, iRow, &zErr);
if( rc!=SQLITE_OK ){
sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr);
@@ -94854,13 +96287,16 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
if( pSorter==0 ){
rc = SQLITE_NOMEM_BKPT;
}else{
+ Btree *pBt = db->aDb[0].pBt;
pSorter->pKeyInfo = pKeyInfo = (KeyInfo*)((u8*)pSorter + sz);
memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo);
pKeyInfo->db = 0;
if( nField && nWorker==0 ){
pKeyInfo->nKeyField = nField;
}
- pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(db->aDb[0].pBt);
+ sqlite3BtreeEnter(pBt);
+ pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(pBt);
+ sqlite3BtreeLeave(pBt);
pSorter->nTask = nWorker + 1;
pSorter->iPrev = (u8)(nWorker - 1);
pSorter->bUseThreads = (pSorter->nTask>1);
@@ -94954,8 +96390,9 @@ static void vdbeSorterWorkDebug(SortSubtask *pTask, const char *zEvent){
fprintf(stderr, "%lld:%d %s\n", t, iTask, zEvent);
}
static void vdbeSorterRewindDebug(const char *zEvent){
- i64 t;
- sqlite3OsCurrentTimeInt64(sqlite3_vfs_find(0), &t);
+ i64 t = 0;
+ sqlite3_vfs *pVfs = sqlite3_vfs_find(0);
+ if( ALWAYS(pVfs) ) sqlite3OsCurrentTimeInt64(pVfs, &t);
fprintf(stderr, "%lld:X %s\n", t, zEvent);
}
static void vdbeSorterPopulateDebug(
@@ -97144,7 +98581,6 @@ struct MemJournal {
int nChunkSize; /* In-memory chunk-size */
int nSpill; /* Bytes of data before flushing */
- int nSize; /* Bytes of data currently in memory */
FileChunk *pFirst; /* Head of in-memory chunk-list */
FilePoint endpoint; /* Pointer to the end of the file */
FilePoint readpoint; /* Pointer to the end of the last xRead() */
@@ -97205,14 +98641,13 @@ static int memjrnlRead(
/*
** Free the list of FileChunk structures headed at MemJournal.pFirst.
*/
-static void memjrnlFreeChunks(MemJournal *p){
+static void memjrnlFreeChunks(FileChunk *pFirst){
FileChunk *pIter;
FileChunk *pNext;
- for(pIter=p->pFirst; pIter; pIter=pNext){
+ for(pIter=pFirst; pIter; pIter=pNext){
pNext = pIter->pNext;
sqlite3_free(pIter);
}
- p->pFirst = 0;
}
/*
@@ -97239,7 +98674,7 @@ static int memjrnlCreateFile(MemJournal *p){
}
if( rc==SQLITE_OK ){
/* No error has occurred. Free the in-memory buffers. */
- memjrnlFreeChunks(&copy);
+ memjrnlFreeChunks(copy.pFirst);
}
}
if( rc!=SQLITE_OK ){
@@ -97322,7 +98757,6 @@ static int memjrnlWrite(
nWrite -= iSpace;
p->endpoint.iOffset += iSpace;
}
- p->nSize = iAmt + iOfst;
}
}
@@ -97330,19 +98764,29 @@ static int memjrnlWrite(
}
/*
-** Truncate the file.
-**
-** If the journal file is already on disk, truncate it there. Or, if it
-** is still in main memory but is being truncated to zero bytes in size,
-** ignore
+** Truncate the in-memory file.
*/
static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){
MemJournal *p = (MemJournal *)pJfd;
- if( ALWAYS(size==0) ){
- memjrnlFreeChunks(p);
- p->nSize = 0;
- p->endpoint.pChunk = 0;
- p->endpoint.iOffset = 0;
+ assert( p->endpoint.pChunk==0 || p->endpoint.pChunk->pNext==0 );
+ if( size<p->endpoint.iOffset ){
+ FileChunk *pIter = 0;
+ if( size==0 ){
+ memjrnlFreeChunks(p->pFirst);
+ p->pFirst = 0;
+ }else{
+ i64 iOff = p->nChunkSize;
+ for(pIter=p->pFirst; ALWAYS(pIter) && iOff<=size; pIter=pIter->pNext){
+ iOff += p->nChunkSize;
+ }
+ if( ALWAYS(pIter) ){
+ memjrnlFreeChunks(pIter->pNext);
+ pIter->pNext = 0;
+ }
+ }
+
+ p->endpoint.pChunk = pIter;
+ p->endpoint.iOffset = size;
p->readpoint.pChunk = 0;
p->readpoint.iOffset = 0;
}
@@ -97354,7 +98798,7 @@ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){
*/
static int memjrnlClose(sqlite3_file *pJfd){
MemJournal *p = (MemJournal *)pJfd;
- memjrnlFreeChunks(p);
+ memjrnlFreeChunks(p->pFirst);
return SQLITE_OK;
}
@@ -97528,7 +98972,7 @@ SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *pVfs){
** Walk all expressions linked into the list of Window objects passed
** as the second argument.
*/
-static int walkWindowList(Walker *pWalker, Window *pList){
+static int walkWindowList(Walker *pWalker, Window *pList, int bOneOnly){
Window *pWin;
for(pWin=pList; pWin; pWin=pWin->pNextWin){
int rc;
@@ -97538,15 +98982,11 @@ static int walkWindowList(Walker *pWalker, Window *pList){
if( rc ) return WRC_Abort;
rc = sqlite3WalkExpr(pWalker, pWin->pFilter);
if( rc ) return WRC_Abort;
-
- /* The next two are purely for calls to sqlite3RenameExprUnmap()
- ** within sqlite3WindowOffsetExpr(). Because of constraints imposed
- ** by sqlite3WindowOffsetExpr(), they can never fail. The results do
- ** not matter anyhow. */
rc = sqlite3WalkExpr(pWalker, pWin->pStart);
- if( NEVER(rc) ) return WRC_Abort;
+ if( rc ) return WRC_Abort;
rc = sqlite3WalkExpr(pWalker, pWin->pEnd);
- if( NEVER(rc) ) return WRC_Abort;
+ if( rc ) return WRC_Abort;
+ if( bOneOnly ) break;
}
return WRC_Continue;
}
@@ -97594,7 +99034,7 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){
}
#ifndef SQLITE_OMIT_WINDOWFUNC
if( ExprHasProperty(pExpr, EP_WinFunc) ){
- if( walkWindowList(pWalker, pExpr->y.pWin) ) return WRC_Abort;
+ if( walkWindowList(pWalker, pExpr->y.pWin, 1) ) return WRC_Abort;
}
#endif
}
@@ -97623,6 +99063,16 @@ SQLITE_PRIVATE int sqlite3WalkExprList(Walker *pWalker, ExprList *p){
}
/*
+** This is a no-op callback for Walker->xSelectCallback2. If this
+** callback is set, then the Select->pWinDefn list is traversed.
+*/
+SQLITE_PRIVATE void sqlite3WalkWinDefnDummyCallback(Walker *pWalker, Select *p){
+ UNUSED_PARAMETER(pWalker);
+ UNUSED_PARAMETER(p);
+ /* No-op */
+}
+
+/*
** Walk all expressions associated with SELECT statement p. Do
** not invoke the SELECT callback on p, but do (of course) invoke
** any expr callbacks and SELECT callbacks that come from subqueries.
@@ -97635,13 +99085,18 @@ SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){
if( sqlite3WalkExpr(pWalker, p->pHaving) ) return WRC_Abort;
if( sqlite3WalkExprList(pWalker, p->pOrderBy) ) return WRC_Abort;
if( sqlite3WalkExpr(pWalker, p->pLimit) ) return WRC_Abort;
-#if !defined(SQLITE_OMIT_WINDOWFUNC) && !defined(SQLITE_OMIT_ALTERTABLE)
- {
- Parse *pParse = pWalker->pParse;
- if( pParse && IN_RENAME_OBJECT ){
+#if !defined(SQLITE_OMIT_WINDOWFUNC)
+ if( p->pWinDefn ){
+ Parse *pParse;
+ if( pWalker->xSelectCallback2==sqlite3WalkWinDefnDummyCallback
+ || ((pParse = pWalker->pParse)!=0 && IN_RENAME_OBJECT)
+#ifndef SQLITE_OMIT_CTE
+ || pWalker->xSelectCallback2==sqlite3SelectPopWith
+#endif
+ ){
/* The following may return WRC_Abort if there are unresolvable
** symbols (e.g. a table that does not exist) in a window definition. */
- int rc = walkWindowList(pWalker, p->pWinDefn);
+ int rc = walkWindowList(pWalker, p->pWinDefn, 0);
return rc;
}
}
@@ -97659,10 +99114,10 @@ SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){
SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){
SrcList *pSrc;
int i;
- struct SrcList_item *pItem;
+ SrcItem *pItem;
pSrc = p->pSrc;
- if( pSrc ){
+ if( ALWAYS(pSrc) ){
for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){
if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){
return WRC_Abort;
@@ -97825,7 +99280,6 @@ static void resolveAlias(
ExprList *pEList, /* A result set */
int iCol, /* A column in the result set. 0..pEList->nExpr-1 */
Expr *pExpr, /* Transform this into an alias to the result set */
- const char *zType, /* "GROUP" or "ORDER" or "" */
int nSubquery /* Number of subqueries that the label is moving */
){
Expr *pOrig; /* The iCol-th column of the result set */
@@ -97837,8 +99291,11 @@ static void resolveAlias(
assert( pOrig!=0 );
db = pParse->db;
pDup = sqlite3ExprDup(db, pOrig, 0);
- if( pDup!=0 ){
- if( zType[0]!='G' ) incrAggFunctionDepth(pDup, nSubquery);
+ if( db->mallocFailed ){
+ sqlite3ExprDelete(db, pDup);
+ pDup = 0;
+ }else{
+ incrAggFunctionDepth(pDup, nSubquery);
if( pExpr->op==TK_COLLATE ){
pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken);
}
@@ -97859,15 +99316,12 @@ static void resolveAlias(
pExpr->flags |= EP_MemToken;
}
if( ExprHasProperty(pExpr, EP_WinFunc) ){
- if( pExpr->y.pWin!=0 ){
+ if( ALWAYS(pExpr->y.pWin!=0) ){
pExpr->y.pWin->pOwner = pExpr;
- }else{
- assert( db->mallocFailed );
}
}
sqlite3DbFree(db, pDup);
}
- ExprSetProperty(pExpr, EP_Alias);
}
@@ -98002,8 +99456,8 @@ static int lookupName(
int cntTab = 0; /* Number of matching table names */
int nSubquery = 0; /* How many levels of subquery */
sqlite3 *db = pParse->db; /* The database connection */
- struct SrcList_item *pItem; /* Use for looping over pSrcList items */
- struct SrcList_item *pMatch = 0; /* The matching pSrcList item */
+ SrcItem *pItem; /* Use for looping over pSrcList items */
+ SrcItem *pMatch = 0; /* The matching pSrcList item */
NameContext *pTopNC = pNC; /* First namecontext in the list */
Schema *pSchema = 0; /* Schema of the expression */
int eNewExprOp = TK_COLUMN; /* New value for pExpr->op on success */
@@ -98124,25 +99578,33 @@ static int lookupName(
#if !defined(SQLITE_OMIT_TRIGGER) || !defined(SQLITE_OMIT_UPSERT)
/* If we have not already resolved the name, then maybe
** it is a new.* or old.* trigger argument reference. Or
- ** maybe it is an excluded.* from an upsert.
+ ** maybe it is an excluded.* from an upsert. Or maybe it is
+ ** a reference in the RETURNING clause to a table being modified.
*/
- if( zDb==0 && zTab!=0 && cntTab==0 ){
+ if( cnt==0 && zDb==0 ){
pTab = 0;
#ifndef SQLITE_OMIT_TRIGGER
if( pParse->pTriggerTab!=0 ){
int op = pParse->eTriggerOp;
assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT );
- if( op!=TK_DELETE && sqlite3StrICmp("new",zTab) == 0 ){
+ if( pParse->bReturning ){
+ if( (pNC->ncFlags & NC_UBaseReg)!=0
+ && (zTab==0 || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0)
+ ){
+ pExpr->iTable = op!=TK_DELETE;
+ pTab = pParse->pTriggerTab;
+ }
+ }else if( op!=TK_DELETE && zTab && sqlite3StrICmp("new",zTab) == 0 ){
pExpr->iTable = 1;
pTab = pParse->pTriggerTab;
- }else if( op!=TK_INSERT && sqlite3StrICmp("old",zTab)==0 ){
+ }else if( op!=TK_INSERT && zTab && sqlite3StrICmp("old",zTab)==0 ){
pExpr->iTable = 0;
pTab = pParse->pTriggerTab;
}
}
#endif /* SQLITE_OMIT_TRIGGER */
#ifndef SQLITE_OMIT_UPSERT
- if( (pNC->ncFlags & NC_UUpsert)!=0 ){
+ if( (pNC->ncFlags & NC_UUpsert)!=0 && zTab!=0 ){
Upsert *pUpsert = pNC->uNC.pUpsert;
if( pUpsert && sqlite3StrICmp("excluded",zTab)==0 ){
pTab = pUpsert->pUpsertSrc->a[0].pTab;
@@ -98170,6 +99632,7 @@ static int lookupName(
}
if( iCol<pTab->nCol ){
cnt++;
+ pMatch = 0;
#ifndef SQLITE_OMIT_UPSERT
if( pExpr->iTable==EXCLUDED_TABLE_NUMBER ){
testcase( iCol==(-1) );
@@ -98181,27 +99644,32 @@ static int lookupName(
pExpr->iTable = pNC->uNC.pUpsert->regData +
sqlite3TableColumnToStorage(pTab, iCol);
eNewExprOp = TK_REGISTER;
- ExprSetProperty(pExpr, EP_Alias);
}
}else
#endif /* SQLITE_OMIT_UPSERT */
{
-#ifndef SQLITE_OMIT_TRIGGER
- if( iCol<0 ){
- pExpr->affExpr = SQLITE_AFF_INTEGER;
- }else if( pExpr->iTable==0 ){
- testcase( iCol==31 );
- testcase( iCol==32 );
- pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
- }else{
- testcase( iCol==31 );
- testcase( iCol==32 );
- pParse->newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
- }
pExpr->y.pTab = pTab;
- pExpr->iColumn = (i16)iCol;
- eNewExprOp = TK_TRIGGER;
+ if( pParse->bReturning ){
+ eNewExprOp = TK_REGISTER;
+ pExpr->iTable = pNC->uNC.iBaseReg + (pTab->nCol+1)*pExpr->iTable +
+ sqlite3TableColumnToStorage(pTab, iCol) + 1;
+ }else{
+ pExpr->iColumn = (i16)iCol;
+ eNewExprOp = TK_TRIGGER;
+#ifndef SQLITE_OMIT_TRIGGER
+ if( iCol<0 ){
+ pExpr->affExpr = SQLITE_AFF_INTEGER;
+ }else if( pExpr->iTable==0 ){
+ testcase( iCol==31 );
+ testcase( iCol==32 );
+ pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
+ }else{
+ testcase( iCol==31 );
+ testcase( iCol==32 );
+ pParse->newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
+ }
#endif /* SQLITE_OMIT_TRIGGER */
+ }
}
}
}
@@ -98241,8 +99709,8 @@ static int lookupName(
** is supported for backwards compatibility only. Hence, we issue a warning
** on sqlite3_log() whenever the capability is used.
*/
- if( (pNC->ncFlags & NC_UEList)!=0
- && cnt==0
+ if( cnt==0
+ && (pNC->ncFlags & NC_UEList)!=0
&& zTab==0
){
pEList = pNC->uNC.pEList;
@@ -98271,7 +99739,7 @@ static int lookupName(
sqlite3ErrorMsg(pParse, "row value misused");
return WRC_Abort;
}
- resolveAlias(pParse, pEList, j, pExpr, "", nSubquery);
+ resolveAlias(pParse, pEList, j, pExpr, nSubquery);
cnt = 1;
pMatch = 0;
assert( zTab==0 && zDb==0 );
@@ -98350,7 +99818,7 @@ static int lookupName(
sqlite3ErrorMsg(pParse, "%s: %s", zErr, zCol);
}
pParse->checkSchema = 1;
- pTopNC->nErr++;
+ pTopNC->nNcErr++;
}
/* If a column from a table in pSrcList is referenced, then record
@@ -98373,18 +99841,24 @@ static int lookupName(
/* Clean up and return
*/
- sqlite3ExprDelete(db, pExpr->pLeft);
- pExpr->pLeft = 0;
- sqlite3ExprDelete(db, pExpr->pRight);
- pExpr->pRight = 0;
+ if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){
+ sqlite3ExprDelete(db, pExpr->pLeft);
+ pExpr->pLeft = 0;
+ sqlite3ExprDelete(db, pExpr->pRight);
+ pExpr->pRight = 0;
+ }
pExpr->op = eNewExprOp;
ExprSetProperty(pExpr, EP_Leaf);
lookupname_end:
if( cnt==1 ){
assert( pNC!=0 );
- if( !ExprHasProperty(pExpr, EP_Alias) ){
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( pParse->db->xAuth
+ && (pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER)
+ ){
sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList);
}
+#endif
/* Increment the nRef value on all name contexts from TopNC up to
** the point where the name matched. */
for(;;){
@@ -98406,7 +99880,7 @@ lookupname_end:
SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSrc, int iCol){
Expr *p = sqlite3ExprAlloc(db, TK_COLUMN, 0, 0);
if( p ){
- struct SrcList_item *pItem = &pSrc->a[iSrc];
+ SrcItem *pItem = &pSrc->a[iSrc];
Table *pTab = p->y.pTab = pItem->pTab;
p->iTable = pItem->iCursor;
if( p->y.pTab->iPKey==iCol ){
@@ -98518,7 +99992,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
*/
case TK_ROW: {
SrcList *pSrcList = pNC->pSrcList;
- struct SrcList_item *pItem;
+ SrcItem *pItem;
assert( pSrcList && pSrcList->nSrc>=1 );
pItem = pSrcList->a;
pExpr->op = TK_COLUMN;
@@ -98529,6 +100003,47 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
break;
}
+ /* An optimization: Attempt to convert
+ **
+ ** "expr IS NOT NULL" --> "TRUE"
+ ** "expr IS NULL" --> "FALSE"
+ **
+ ** if we can prove that "expr" is never NULL. Call this the
+ ** "NOT NULL strength reduction optimization".
+ **
+ ** If this optimization occurs, also restore the NameContext ref-counts
+ ** to the state they where in before the "column" LHS expression was
+ ** resolved. This prevents "column" from being counted as having been
+ ** referenced, which might prevent a SELECT from being erroneously
+ ** marked as correlated.
+ */
+ case TK_NOTNULL:
+ case TK_ISNULL: {
+ int anRef[8];
+ NameContext *p;
+ int i;
+ for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){
+ anRef[i] = p->nRef;
+ }
+ sqlite3WalkExpr(pWalker, pExpr->pLeft);
+ if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){
+ if( pExpr->op==TK_NOTNULL ){
+ pExpr->u.zToken = "true";
+ ExprSetProperty(pExpr, EP_IsTrue);
+ }else{
+ pExpr->u.zToken = "false";
+ ExprSetProperty(pExpr, EP_IsFalse);
+ }
+ pExpr->op = TK_TRUEFALSE;
+ for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){
+ p->nRef = anRef[i];
+ }
+ sqlite3ExprDelete(pParse->db, pExpr->pLeft);
+ pExpr->pLeft = 0;
+ }
+ return WRC_Prune;
+ }
+
/* A column name: ID
** Or table name and column name: ID.ID
** Or a database, table and column: ID.ID.ID
@@ -98610,7 +100125,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
sqlite3ErrorMsg(pParse,
"second argument to likelihood() must be a "
"constant between 0.0 and 1.0");
- pNC->nErr++;
+ pNC->nNcErr++;
}
}else{
/* EVIDENCE-OF: R-61304-29449 The unlikely(X) function is
@@ -98632,7 +100147,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
if( auth==SQLITE_DENY ){
sqlite3ErrorMsg(pParse, "not authorized to use function: %s",
pDef->zName);
- pNC->nErr++;
+ pNC->nNcErr++;
}
pExpr->op = TK_NULL;
return WRC_Prune;
@@ -98688,7 +100203,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
sqlite3ErrorMsg(pParse,
"%.*s() may not be used as a window function", nId, zId
);
- pNC->nErr++;
+ pNC->nNcErr++;
}else if(
(is_agg && (pNC->ncFlags & NC_AllowAgg)==0)
|| (is_agg && (pDef->funcFlags&SQLITE_FUNC_WINDOW) && !pWin)
@@ -98701,13 +100216,13 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
zType = "aggregate";
}
sqlite3ErrorMsg(pParse, "misuse of %s function %.*s()",zType,nId,zId);
- pNC->nErr++;
+ pNC->nNcErr++;
is_agg = 0;
}
#else
if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) ){
sqlite3ErrorMsg(pParse,"misuse of aggregate function %.*s()",nId,zId);
- pNC->nErr++;
+ pNC->nNcErr++;
is_agg = 0;
}
#endif
@@ -98717,11 +100232,11 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
#endif
){
sqlite3ErrorMsg(pParse, "no such function: %.*s", nId, zId);
- pNC->nErr++;
+ pNC->nNcErr++;
}else if( wrong_num_args ){
sqlite3ErrorMsg(pParse,"wrong number of arguments to function %.*s()",
nId, zId);
- pNC->nErr++;
+ pNC->nNcErr++;
}
#ifndef SQLITE_OMIT_WINDOWFUNC
else if( is_agg==0 && ExprHasProperty(pExpr, EP_WinFunc) ){
@@ -98729,7 +100244,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
"FILTER may not be used with non-aggregate %.*s()",
nId, zId
);
- pNC->nErr++;
+ pNC->nNcErr++;
}
#endif
if( is_agg ){
@@ -98756,6 +100271,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
assert( pWin==pExpr->y.pWin );
if( IN_RENAME_OBJECT==0 ){
sqlite3WindowUpdate(pParse, pSel ? pSel->pWinDefn : 0, pWin, pDef);
+ if( pParse->db->mallocFailed ) break;
}
sqlite3WalkExprList(pWalker, pWin->pPartition);
sqlite3WalkExprList(pWalker, pWin->pOrderBy);
@@ -98830,7 +100346,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
assert( !ExprHasProperty(pExpr, EP_Reduced) );
/* Handle special cases of "x IS TRUE", "x IS FALSE", "x IS NOT TRUE",
** and "x IS NOT FALSE". */
- if( pRight && pRight->op==TK_ID ){
+ if( ALWAYS(pRight) && (pRight->op==TK_ID || pRight->op==TK_TRUEFALSE) ){
int rc = resolveExprStep(pWalker, pRight);
if( rc==WRC_Abort ) return WRC_Abort;
if( pRight->op==TK_TRUEFALSE ){
@@ -98952,11 +100468,11 @@ static int resolveOrderByTermToExprList(
nc.pParse = pParse;
nc.pSrcList = pSelect->pSrc;
nc.uNC.pEList = pEList;
- nc.ncFlags = NC_AllowAgg|NC_UEList;
- nc.nErr = 0;
+ nc.ncFlags = NC_AllowAgg|NC_UEList|NC_NoSelect;
+ nc.nNcErr = 0;
db = pParse->db;
savedSuppErr = db->suppressErr;
- if( IN_RENAME_OBJECT==0 ) db->suppressErr = 1;
+ db->suppressErr = 1;
rc = sqlite3ResolveExprNames(&nc, pE);
db->suppressErr = savedSuppErr;
if( rc ) return 0;
@@ -99039,6 +100555,7 @@ static int resolveCompoundOrderBy(
Expr *pE, *pDup;
if( pItem->done ) continue;
pE = sqlite3ExprSkipCollateAndLikely(pItem->pExpr);
+ if( NEVER(pE==0) ) continue;
if( sqlite3ExprIsInteger(pE, &iCol) ){
if( iCol<=0 || iCol>pEList->nExpr ){
resolveOutOfRangeError(pParse, "ORDER", i+1, pEList->nExpr);
@@ -99054,29 +100571,24 @@ static int resolveCompoundOrderBy(
** Once the comparisons are finished, the duplicate expression
** is deleted.
**
- ** Or, if this is running as part of an ALTER TABLE operation,
- ** resolve the symbols in the actual expression, not a duplicate.
- ** And, if one of the comparisons is successful, leave the expression
- ** as is instead of transforming it to an integer as in the usual
- ** case. This allows the code in alter.c to modify column
- ** refererences within the ORDER BY expression as required. */
- if( IN_RENAME_OBJECT ){
- pDup = pE;
- }else{
- pDup = sqlite3ExprDup(db, pE, 0);
- }
+ ** If this is running as part of an ALTER TABLE operation and
+ ** the symbols resolve successfully, also resolve the symbols in the
+ ** actual expression. This allows the code in alter.c to modify
+ ** column references within the ORDER BY expression as required. */
+ pDup = sqlite3ExprDup(db, pE, 0);
if( !db->mallocFailed ){
assert(pDup);
iCol = resolveOrderByTermToExprList(pParse, pSelect, pDup);
+ if( IN_RENAME_OBJECT && iCol>0 ){
+ resolveOrderByTermToExprList(pParse, pSelect, pE);
+ }
}
- if( !IN_RENAME_OBJECT ){
- sqlite3ExprDelete(db, pDup);
- }
+ sqlite3ExprDelete(db, pDup);
}
}
if( iCol>0 ){
/* Convert the ORDER BY term into an integer column number iCol,
- ** taking care to preserve the COLLATE clause if it exists */
+ ** taking care to preserve the COLLATE clause if it exists. */
if( !IN_RENAME_OBJECT ){
Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0);
if( pNew==0 ) return 1;
@@ -99145,8 +100657,7 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(
resolveOutOfRangeError(pParse, zType, i+1, pEList->nExpr);
return 1;
}
- resolveAlias(pParse, pEList, pItem->u.x.iOrderByCol-1, pItem->pExpr,
- zType,0);
+ resolveAlias(pParse, pEList, pItem->u.x.iOrderByCol-1, pItem->pExpr,0);
}
}
return 0;
@@ -99212,12 +100723,13 @@ static int resolveOrderGroupBy(
Parse *pParse; /* Parsing context */
int nResult; /* Number of terms in the result set */
- if( pOrderBy==0 ) return 0;
+ assert( pOrderBy!=0 );
nResult = pSelect->pEList->nExpr;
pParse = pNC->pParse;
for(i=0, pItem=pOrderBy->a; i<pOrderBy->nExpr; i++, pItem++){
Expr *pE = pItem->pExpr;
Expr *pE2 = sqlite3ExprSkipCollateAndLikely(pE);
+ if( NEVER(pE2==0) ) continue;
if( zType[0]!='G' ){
iCol = resolveAsName(pParse, pSelect->pEList, pE2);
if( iCol>0 ){
@@ -99301,8 +100813,10 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
while( p ){
assert( (p->selFlags & SF_Expanded)!=0 );
assert( (p->selFlags & SF_Resolved)==0 );
+ assert( db->suppressErr==0 ); /* SF_Resolved not set if errors suppressed */
p->selFlags |= SF_Resolved;
+
/* Resolve the expressions in the LIMIT and OFFSET clauses. These
** are not allowed to refer to any names, so pass an empty NameContext.
*/
@@ -99330,27 +100844,26 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
/* Recursively resolve names in all subqueries
*/
for(i=0; i<p->pSrc->nSrc; i++){
- struct SrcList_item *pItem = &p->pSrc->a[i];
+ SrcItem *pItem = &p->pSrc->a[i];
if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){
- NameContext *pNC; /* Used to iterate name contexts */
- int nRef = 0; /* Refcount for pOuterNC and outer contexts */
+ int nRef = pOuterNC ? pOuterNC->nRef : 0;
const char *zSavedContext = pParse->zAuthContext;
- /* Count the total number of references to pOuterNC and all of its
- ** parent contexts. After resolving references to expressions in
- ** pItem->pSelect, check if this value has changed. If so, then
- ** SELECT statement pItem->pSelect must be correlated. Set the
- ** pItem->fg.isCorrelated flag if this is the case. */
- for(pNC=pOuterNC; pNC; pNC=pNC->pNext) nRef += pNC->nRef;
-
if( pItem->zName ) pParse->zAuthContext = pItem->zName;
sqlite3ResolveSelectNames(pParse, pItem->pSelect, pOuterNC);
pParse->zAuthContext = zSavedContext;
if( pParse->nErr || db->mallocFailed ) return WRC_Abort;
- for(pNC=pOuterNC; pNC; pNC=pNC->pNext) nRef -= pNC->nRef;
- assert( pItem->fg.isCorrelated==0 && nRef<=0 );
- pItem->fg.isCorrelated = (nRef!=0);
+ /* If the number of references to the outer context changed when
+ ** expressions in the sub-select were resolved, the sub-select
+ ** is correlated. It is not required to check the refcount on any
+ ** but the innermost outer context object, as lookupName() increments
+ ** the refcount on all contexts between the current one and the
+ ** context containing the column when it resolves a name. */
+ if( pOuterNC ){
+ assert( pItem->fg.isCorrelated==0 && pOuterNC->nRef>=nRef );
+ pItem->fg.isCorrelated = (pOuterNC->nRef>nRef);
+ }
}
}
@@ -99377,13 +100890,6 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
sNC.ncFlags &= ~NC_AllowAgg;
}
- /* If a HAVING clause is present, then there must be a GROUP BY clause.
- */
- if( p->pHaving && !pGroupBy ){
- sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING");
- return WRC_Abort;
- }
-
/* Add the output column list to the name-context before parsing the
** other expressions in the SELECT statement. This is so that
** expressions in the WHERE clause (etc.) can refer to expressions by
@@ -99392,15 +100898,21 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
** Minor point: If this is the case, then the expression will be
** re-evaluated for each reference to it.
*/
- assert( (sNC.ncFlags & (NC_UAggInfo|NC_UUpsert))==0 );
+ assert( (sNC.ncFlags & (NC_UAggInfo|NC_UUpsert|NC_UBaseReg))==0 );
sNC.uNC.pEList = p->pEList;
sNC.ncFlags |= NC_UEList;
- if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort;
+ if( p->pHaving ){
+ if( !pGroupBy ){
+ sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING");
+ return WRC_Abort;
+ }
+ if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort;
+ }
if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort;
/* Resolve names in table-valued-function arguments */
for(i=0; i<p->pSrc->nSrc; i++){
- struct SrcList_item *pItem = &p->pSrc->a[i];
+ SrcItem *pItem = &p->pSrc->a[i];
if( pItem->fg.isTabFunc
&& sqlite3ResolveExprListNames(&sNC, pItem->u1.pFuncArg)
){
@@ -99408,6 +100920,19 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
}
}
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( IN_RENAME_OBJECT ){
+ Window *pWin;
+ for(pWin=p->pWinDefn; pWin; pWin=pWin->pNextWin){
+ if( sqlite3ResolveExprListNames(&sNC, pWin->pOrderBy)
+ || sqlite3ResolveExprListNames(&sNC, pWin->pPartition)
+ ){
+ return WRC_Abort;
+ }
+ }
+ }
+#endif
+
/* The ORDER BY and GROUP BY clauses may not refer to terms in
** outer queries
*/
@@ -99435,7 +100960,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
** is not detected until much later, and so we need to go ahead and
** resolve those symbols on the incorrect ORDER BY for consistency.
*/
- if( isCompound<=nCompound /* Defer right-most ORDER BY of a compound */
+ if( p->pOrderBy!=0
+ && isCompound<=nCompound /* Defer right-most ORDER BY of a compound */
&& resolveOrderGroupBy(&sNC, p, p->pOrderBy, "ORDER")
){
return WRC_Abort;
@@ -99463,19 +100989,6 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
}
}
-#ifndef SQLITE_OMIT_WINDOWFUNC
- if( IN_RENAME_OBJECT ){
- Window *pWin;
- for(pWin=p->pWinDefn; pWin; pWin=pWin->pNextWin){
- if( sqlite3ResolveExprListNames(&sNC, pWin->pOrderBy)
- || sqlite3ResolveExprListNames(&sNC, pWin->pPartition)
- ){
- return WRC_Abort;
- }
- }
- }
-#endif
-
/* If this is part of a compound SELECT, check that it has the right
** number of expressions in the select list. */
if( p->pNext && p->pEList->nExpr!=p->pNext->pEList->nExpr ){
@@ -99559,7 +101072,7 @@ SQLITE_PRIVATE int sqlite3ResolveExprNames(
pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin);
w.pParse = pNC->pParse;
w.xExprCallback = resolveExprStep;
- w.xSelectCallback = resolveSelectStep;
+ w.xSelectCallback = (pNC->ncFlags & NC_NoSelect) ? 0 : resolveSelectStep;
w.xSelectCallback2 = 0;
w.u.pNC = pNC;
#if SQLITE_MAX_EXPR_DEPTH>0
@@ -99578,7 +101091,7 @@ SQLITE_PRIVATE int sqlite3ResolveExprNames(
testcase( pNC->ncFlags & NC_HasWin );
ExprSetProperty(pExpr, pNC->ncFlags & (NC_HasAgg|NC_HasWin) );
pNC->ncFlags |= savedHasAgg;
- return pNC->nErr>0 || w.pParse->nErr>0;
+ return pNC->nNcErr>0 || w.pParse->nErr>0;
}
/*
@@ -99623,7 +101136,7 @@ SQLITE_PRIVATE int sqlite3ResolveExprListNames(
savedHasAgg |= pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin);
pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin);
}
- if( pNC->nErr>0 || w.pParse->nErr>0 ) return WRC_Abort;
+ if( w.pParse->nErr>0 ) return WRC_Abort;
}
pNC->ncFlags |= savedHasAgg;
return WRC_Continue;
@@ -99758,12 +101271,18 @@ SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table *pTab, int iCol){
*/
SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){
int op;
- while( ExprHasProperty(pExpr, EP_Skip) ){
- assert( pExpr->op==TK_COLLATE || pExpr->op==TK_IF_NULL_ROW );
+ while( ExprHasProperty(pExpr, EP_Skip|EP_IfNullRow) ){
+ assert( pExpr->op==TK_COLLATE
+ || pExpr->op==TK_IF_NULL_ROW
+ || (pExpr->op==TK_REGISTER && pExpr->op2==TK_IF_NULL_ROW) );
pExpr = pExpr->pLeft;
assert( pExpr!=0 );
}
op = pExpr->op;
+ if( op==TK_REGISTER ) op = pExpr->op2;
+ if( (op==TK_COLUMN || op==TK_AGG_COLUMN) && pExpr->y.pTab ){
+ return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn);
+ }
if( op==TK_SELECT ){
assert( pExpr->flags&EP_xIsSelect );
assert( pExpr->x.pSelect!=0 );
@@ -99771,16 +101290,12 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){
assert( pExpr->x.pSelect->pEList->a[0].pExpr!=0 );
return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr);
}
- if( op==TK_REGISTER ) op = pExpr->op2;
#ifndef SQLITE_OMIT_CAST
if( op==TK_CAST ){
assert( !ExprHasProperty(pExpr, EP_IntValue) );
return sqlite3AffinityType(pExpr->u.zToken, 0);
}
#endif
- if( (op==TK_AGG_COLUMN || op==TK_COLUMN) && pExpr->y.pTab ){
- return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn);
- }
if( op==TK_SELECT_COLUMN ){
assert( pExpr->pLeft->flags&EP_xIsSelect );
return sqlite3ExprAffinity(
@@ -99829,7 +101344,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, con
*/
SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr *pExpr){
while( pExpr && ExprHasProperty(pExpr, EP_Skip) ){
- assert( pExpr->op==TK_COLLATE || pExpr->op==TK_IF_NULL_ROW );
+ assert( pExpr->op==TK_COLLATE );
pExpr = pExpr->pLeft;
}
return pExpr;
@@ -99848,7 +101363,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr *pExpr){
assert( pExpr->op==TK_FUNCTION );
pExpr = pExpr->x.pList->a[0].pExpr;
}else{
- assert( pExpr->op==TK_COLLATE || pExpr->op==TK_IF_NULL_ROW );
+ assert( pExpr->op==TK_COLLATE );
pExpr = pExpr->pLeft;
}
}
@@ -100157,7 +101672,7 @@ SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){
** been positioned.
*/
SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){
- assert( i<sqlite3ExprVectorSize(pVector) );
+ assert( i<sqlite3ExprVectorSize(pVector) || pVector->op==TK_ERROR );
if( sqlite3ExprIsVector(pVector) ){
assert( pVector->op2==0 || pVector->op==TK_REGISTER );
if( pVector->op==TK_SELECT || pVector->op2==TK_SELECT ){
@@ -100273,7 +101788,7 @@ static int exprVectorRegister(
int *pRegFree /* OUT: Temp register to free */
){
u8 op = pVector->op;
- assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT );
+ assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT || op==TK_ERROR );
if( op==TK_REGISTER ){
*ppExpr = sqlite3VectorFieldSubexpr(pVector, iField);
return pVector->iTable+iField;
@@ -100282,8 +101797,11 @@ static int exprVectorRegister(
*ppExpr = pVector->x.pSelect->pEList->a[iField].pExpr;
return regSelect+iField;
}
- *ppExpr = pVector->x.pList->a[iField].pExpr;
- return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree);
+ if( op==TK_VECTOR ){
+ *ppExpr = pVector->x.pList->a[iField].pExpr;
+ return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree);
+ }
+ return 0;
}
/*
@@ -100312,6 +101830,7 @@ static void codeVectorCompare(
int regLeft = 0;
int regRight = 0;
u8 opx = op;
+ int addrCmp = 0;
int addrDone = sqlite3VdbeMakeLabel(pParse);
int isCommuted = ExprHasProperty(pExpr,EP_Commuted);
@@ -100331,21 +101850,24 @@ static void codeVectorCompare(
assert( p5==0 || pExpr->op!=op );
assert( p5==SQLITE_NULLEQ || pExpr->op==op );
- p5 |= SQLITE_STOREP2;
- if( opx==TK_LE ) opx = TK_LT;
- if( opx==TK_GE ) opx = TK_GT;
+ if( op==TK_LE ) opx = TK_LT;
+ if( op==TK_GE ) opx = TK_GT;
+ if( op==TK_NE ) opx = TK_EQ;
regLeft = exprCodeSubselect(pParse, pLeft);
regRight = exprCodeSubselect(pParse, pRight);
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, dest);
for(i=0; 1 /*Loop exits by "break"*/; i++){
int regFree1 = 0, regFree2 = 0;
- Expr *pL, *pR;
+ Expr *pL = 0, *pR = 0;
int r1, r2;
assert( i>=0 && i<nLeft );
+ if( addrCmp ) sqlite3VdbeJumpHere(v, addrCmp);
r1 = exprVectorRegister(pParse, pLeft, i, regLeft, &pL, &regFree1);
r2 = exprVectorRegister(pParse, pRight, i, regRight, &pR, &regFree2);
- codeCompare(pParse, pL, pR, opx, r1, r2, dest, p5, isCommuted);
+ addrCmp = sqlite3VdbeCurrentAddr(v);
+ codeCompare(pParse, pL, pR, opx, r1, r2, addrDone, p5, isCommuted);
testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt);
testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le);
testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt);
@@ -100354,26 +101876,32 @@ static void codeVectorCompare(
testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne);
sqlite3ReleaseTempReg(pParse, regFree1);
sqlite3ReleaseTempReg(pParse, regFree2);
+ if( (opx==TK_LT || opx==TK_GT) && i<nLeft-1 ){
+ addrCmp = sqlite3VdbeAddOp0(v, OP_ElseEq);
+ testcase(opx==TK_LT); VdbeCoverageIf(v,opx==TK_LT);
+ testcase(opx==TK_GT); VdbeCoverageIf(v,opx==TK_GT);
+ }
+ if( p5==SQLITE_NULLEQ ){
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, dest);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_ZeroOrNull, r1, dest, r2);
+ }
if( i==nLeft-1 ){
break;
}
if( opx==TK_EQ ){
- sqlite3VdbeAddOp2(v, OP_IfNot, dest, addrDone); VdbeCoverage(v);
- p5 |= SQLITE_KEEPNULL;
- }else if( opx==TK_NE ){
- sqlite3VdbeAddOp2(v, OP_If, dest, addrDone); VdbeCoverage(v);
- p5 |= SQLITE_KEEPNULL;
+ sqlite3VdbeAddOp2(v, OP_NotNull, dest, addrDone); VdbeCoverage(v);
}else{
assert( op==TK_LT || op==TK_GT || op==TK_LE || op==TK_GE );
- sqlite3VdbeAddOp2(v, OP_ElseNotEq, 0, addrDone);
- VdbeCoverageIf(v, op==TK_LT);
- VdbeCoverageIf(v, op==TK_GT);
- VdbeCoverageIf(v, op==TK_LE);
- VdbeCoverageIf(v, op==TK_GE);
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, addrDone);
if( i==nLeft-2 ) opx = op;
}
}
+ sqlite3VdbeJumpHere(v, addrCmp);
sqlite3VdbeResolveLabel(v, addrDone);
+ if( op==TK_NE ){
+ sqlite3VdbeAddOp2(v, OP_Not, dest, dest);
+ }
}
#if SQLITE_MAX_EXPR_DEPTH>0
@@ -100482,6 +102010,7 @@ SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){
** Expr.flags.
*/
SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){
+ if( pParse->nErr ) return;
if( p && p->x.pList && !ExprHasProperty(p, EP_xIsSelect) ){
p->flags |= EP_Propagate & sqlite3ExprListFlags(p->x.pList);
}
@@ -100658,8 +102187,8 @@ SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){
}else if( (ExprAlwaysFalse(pLeft) || ExprAlwaysFalse(pRight))
&& !IN_RENAME_OBJECT
){
- sqlite3ExprDelete(db, pLeft);
- sqlite3ExprDelete(db, pRight);
+ sqlite3ExprDeferredDelete(pParse, pLeft);
+ sqlite3ExprDeferredDelete(pParse, pRight);
return sqlite3Expr(db, TK_INTEGER, "0");
}else{
return sqlite3PExpr(pParse, TK_AND, pLeft, pRight);
@@ -100856,6 +102385,22 @@ SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){
if( p ) sqlite3ExprDeleteNN(db, p);
}
+
+/*
+** Arrange to cause pExpr to be deleted when the pParse is deleted.
+** This is similar to sqlite3ExprDelete() except that the delete is
+** deferred untilthe pParse is deleted.
+**
+** The pExpr might be deleted immediately on an OOM error.
+**
+** The deferred delete is (currently) implemented by adding the
+** pExpr to the pParse->pConstExpr list with a register number of 0.
+*/
+SQLITE_PRIVATE void sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){
+ pParse->pConstExpr =
+ sqlite3ExprListAppend(pParse, pParse->pConstExpr, pExpr);
+}
+
/* Invoke sqlite3RenameExprUnmap() and sqlite3ExprDelete() on the
** expression.
*/
@@ -100998,6 +102543,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
if( pzBuffer ){
zAlloc = *pzBuffer;
staticFlag = EP_Static;
+ assert( zAlloc!=0 );
}else{
zAlloc = sqlite3DbMallocRawNN(db, dupedExprSize(p, dupFlags));
staticFlag = 0;
@@ -101076,7 +102622,8 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
if( pNew->op==TK_SELECT_COLUMN ){
pNew->pLeft = p->pLeft;
assert( p->iColumn==0 || p->pRight==0 );
- assert( p->pRight==0 || p->pRight==p->pLeft );
+ assert( p->pRight==0 || p->pRight==p->pLeft
+ || ExprHasProperty(p->pLeft, EP_Subquery) );
}else{
pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0);
}
@@ -101093,7 +102640,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
** and the db->mallocFailed flag set.
*/
#ifndef SQLITE_OMIT_CTE
-static With *withDup(sqlite3 *db, With *p){
+SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p){
With *pRet = 0;
if( p ){
sqlite3_int64 nByte = sizeof(*p) + sizeof(p->a[0]) * (p->nCte-1);
@@ -101111,7 +102658,7 @@ static With *withDup(sqlite3 *db, With *p){
return pRet;
}
#else
-# define withDup(x,y) 0
+# define sqlite3WithDup(x,y) 0
#endif
#ifndef SQLITE_OMIT_WINDOWFUNC
@@ -101178,6 +102725,7 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags)
pNew = sqlite3DbMallocRawNN(db, sqlite3DbMallocSize(db, p));
if( pNew==0 ) return 0;
pNew->nExpr = p->nExpr;
+ pNew->nAlloc = p->nAlloc;
pItem = pNew->a;
pOldItem = p->a;
for(i=0; i<p->nExpr; i++, pItem++, pOldItem++){
@@ -101190,7 +102738,8 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags)
){
assert( pNewExpr->iColumn==0 || i>0 );
if( pNewExpr->iColumn==0 ){
- assert( pOldExpr->pLeft==pOldExpr->pRight );
+ assert( pOldExpr->pLeft==pOldExpr->pRight
+ || ExprHasProperty(pOldExpr->pLeft, EP_Subquery) );
pPriorSelectCol = pNewExpr->pLeft = pNewExpr->pRight;
}else{
assert( i>0 );
@@ -101230,8 +102779,8 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){
if( pNew==0 ) return 0;
pNew->nSrc = pNew->nAlloc = p->nSrc;
for(i=0; i<p->nSrc; i++){
- struct SrcList_item *pNewItem = &pNew->a[i];
- struct SrcList_item *pOldItem = &p->a[i];
+ SrcItem *pNewItem = &pNew->a[i];
+ SrcItem *pOldItem = &p->a[i];
Table *pTab;
pNewItem->pSchema = pOldItem->pSchema;
pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase);
@@ -101244,7 +102793,10 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){
if( pNewItem->fg.isIndexedBy ){
pNewItem->u1.zIndexedBy = sqlite3DbStrDup(db, pOldItem->u1.zIndexedBy);
}
- pNewItem->pIBIndex = pOldItem->pIBIndex;
+ pNewItem->u2 = pOldItem->u2;
+ if( pNewItem->fg.isCte ){
+ pNewItem->u2.pCteUse->nUse++;
+ }
if( pNewItem->fg.isTabFunc ){
pNewItem->u1.pFuncArg =
sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags);
@@ -101310,13 +102862,21 @@ SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){
pNew->addrOpenEphm[0] = -1;
pNew->addrOpenEphm[1] = -1;
pNew->nSelectRow = p->nSelectRow;
- pNew->pWith = withDup(db, p->pWith);
+ pNew->pWith = sqlite3WithDup(db, p->pWith);
#ifndef SQLITE_OMIT_WINDOWFUNC
pNew->pWin = 0;
pNew->pWinDefn = sqlite3WindowListDup(db, p->pWinDefn);
if( p->pWin && db->mallocFailed==0 ) gatherSelectWindows(pNew);
#endif
pNew->selId = p->selId;
+ if( db->mallocFailed ){
+ /* Any prior OOM might have left the Select object incomplete.
+ ** Delete the whole thing rather than allow an incomplete Select
+ ** to be used by the code generator. */
+ pNew->pNext = 0;
+ sqlite3SelectDelete(db, pNew);
+ break;
+ }
*pp = pNew;
pp = &pNew->pPrior;
pNext = pNew;
@@ -101347,41 +102907,64 @@ SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){
** NULL is returned. If non-NULL is returned, then it is guaranteed
** that the new entry was successfully appended.
*/
+static const struct ExprList_item zeroItem = {0};
+SQLITE_PRIVATE SQLITE_NOINLINE ExprList *sqlite3ExprListAppendNew(
+ sqlite3 *db, /* Database handle. Used for memory allocation */
+ Expr *pExpr /* Expression to be appended. Might be NULL */
+){
+ struct ExprList_item *pItem;
+ ExprList *pList;
+
+ pList = sqlite3DbMallocRawNN(db, sizeof(ExprList)+sizeof(pList->a[0])*4 );
+ if( pList==0 ){
+ sqlite3ExprDelete(db, pExpr);
+ return 0;
+ }
+ pList->nAlloc = 4;
+ pList->nExpr = 1;
+ pItem = &pList->a[0];
+ *pItem = zeroItem;
+ pItem->pExpr = pExpr;
+ return pList;
+}
+SQLITE_PRIVATE SQLITE_NOINLINE ExprList *sqlite3ExprListAppendGrow(
+ sqlite3 *db, /* Database handle. Used for memory allocation */
+ ExprList *pList, /* List to which to append. Might be NULL */
+ Expr *pExpr /* Expression to be appended. Might be NULL */
+){
+ struct ExprList_item *pItem;
+ ExprList *pNew;
+ pList->nAlloc *= 2;
+ pNew = sqlite3DbRealloc(db, pList,
+ sizeof(*pList)+(pList->nAlloc-1)*sizeof(pList->a[0]));
+ if( pNew==0 ){
+ sqlite3ExprListDelete(db, pList);
+ sqlite3ExprDelete(db, pExpr);
+ return 0;
+ }else{
+ pList = pNew;
+ }
+ pItem = &pList->a[pList->nExpr++];
+ *pItem = zeroItem;
+ pItem->pExpr = pExpr;
+ return pList;
+}
SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(
Parse *pParse, /* Parsing context */
ExprList *pList, /* List to which to append. Might be NULL */
Expr *pExpr /* Expression to be appended. Might be NULL */
){
struct ExprList_item *pItem;
- sqlite3 *db = pParse->db;
- assert( db!=0 );
if( pList==0 ){
- pList = sqlite3DbMallocRawNN(db, sizeof(ExprList) );
- if( pList==0 ){
- goto no_mem;
- }
- pList->nExpr = 0;
- }else if( (pList->nExpr & (pList->nExpr-1))==0 ){
- ExprList *pNew;
- pNew = sqlite3DbRealloc(db, pList,
- sizeof(*pList)+(2*(sqlite3_int64)pList->nExpr-1)*sizeof(pList->a[0]));
- if( pNew==0 ){
- goto no_mem;
- }
- pList = pNew;
+ return sqlite3ExprListAppendNew(pParse->db,pExpr);
+ }
+ if( pList->nAlloc<pList->nExpr+1 ){
+ return sqlite3ExprListAppendGrow(pParse->db,pList,pExpr);
}
pItem = &pList->a[pList->nExpr++];
- assert( offsetof(struct ExprList_item,zEName)==sizeof(pItem->pExpr) );
- assert( offsetof(struct ExprList_item,pExpr)==0 );
- memset(&pItem->zEName,0,sizeof(*pItem)-offsetof(struct ExprList_item,zEName));
+ *pItem = zeroItem;
pItem->pExpr = pExpr;
return pList;
-
-no_mem:
- /* Avoid leaking memory if malloc has failed. */
- sqlite3ExprDelete(db, pExpr);
- sqlite3ExprListDelete(db, pList);
- return 0;
}
/*
@@ -101994,8 +103577,10 @@ SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){
*/
SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){
u8 op;
+ assert( p!=0 );
while( p->op==TK_UPLUS || p->op==TK_UMINUS ){
p = p->pLeft;
+ assert( p!=0 );
}
op = p->op;
if( op==TK_REGISTER ) op = p->op2;
@@ -102282,7 +103867,7 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
/* Code an OP_Transaction and OP_TableLock for <table>. */
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
- assert( iDb>=0 && iDb<SQLITE_MAX_ATTACHED );
+ assert( iDb>=0 && iDb<SQLITE_MAX_DB );
sqlite3CodeVerifySchema(pParse, iDb);
sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
@@ -102627,19 +104212,23 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN(
/* If the LHS and RHS of the IN operator do not match, that
** error will have been caught long before we reach this point. */
if( ALWAYS(pEList->nExpr==nVal) ){
+ Select *pCopy;
SelectDest dest;
int i;
+ int rc;
sqlite3SelectDestInit(&dest, SRT_Set, iTab);
dest.zAffSdst = exprINAffinity(pParse, pExpr);
pSelect->iLimit = 0;
testcase( pSelect->selFlags & SF_Distinct );
testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */
- if( sqlite3Select(pParse, pSelect, &dest) ){
- sqlite3DbFree(pParse->db, dest.zAffSdst);
+ pCopy = sqlite3SelectDup(pParse->db, pSelect, 0);
+ rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest);
+ sqlite3SelectDelete(pParse->db, pCopy);
+ sqlite3DbFree(pParse->db, dest.zAffSdst);
+ if( rc ){
sqlite3KeyInfoUnref(pKeyInfo);
return;
}
- sqlite3DbFree(pParse->db, dest.zAffSdst);
assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */
assert( pEList!=0 );
assert( pEList->nExpr>0 );
@@ -102738,12 +104327,30 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
Vdbe *v = pParse->pVdbe;
assert( v!=0 );
+ if( pParse->nErr ) return 0;
testcase( pExpr->op==TK_EXISTS );
testcase( pExpr->op==TK_SELECT );
assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT );
assert( ExprHasProperty(pExpr, EP_xIsSelect) );
pSel = pExpr->x.pSelect;
+ /* If this routine has already been coded, then invoke it as a
+ ** subroutine. */
+ if( ExprHasProperty(pExpr, EP_Subrtn) ){
+ ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId));
+ sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn,
+ pExpr->y.sub.iAddr);
+ return pExpr->iTable;
+ }
+
+ /* Begin coding the subroutine */
+ ExprSetProperty(pExpr, EP_Subrtn);
+ pExpr->y.sub.regReturn = ++pParse->nMem;
+ pExpr->y.sub.iAddr =
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
+ VdbeComment((v, "return address"));
+
+
/* The evaluation of the EXISTS/SELECT must be repeated every time it
** is encountered if any of the following is true:
**
@@ -102755,22 +104362,6 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
** save the results, and reuse the same result on subsequent invocations.
*/
if( !ExprHasProperty(pExpr, EP_VarSelect) ){
- /* If this routine has already been coded, then invoke it as a
- ** subroutine. */
- if( ExprHasProperty(pExpr, EP_Subrtn) ){
- ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId));
- sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn,
- pExpr->y.sub.iAddr);
- return pExpr->iTable;
- }
-
- /* Begin coding the subroutine */
- ExprSetProperty(pExpr, EP_Subrtn);
- pExpr->y.sub.regReturn = ++pParse->nMem;
- pExpr->y.sub.iAddr =
- sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
- VdbeComment((v, "return address"));
-
addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
}
@@ -102819,19 +104410,22 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
}
pSel->iLimit = 0;
if( sqlite3Select(pParse, pSel, &dest) ){
+ if( pParse->nErr ){
+ pExpr->op2 = pExpr->op;
+ pExpr->op = TK_ERROR;
+ }
return 0;
}
pExpr->iTable = rReg = dest.iSDParm;
ExprSetVVAProperty(pExpr, EP_NoReduce);
if( addrOnce ){
sqlite3VdbeJumpHere(v, addrOnce);
-
- /* Subroutine return */
- sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn);
- sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1);
- sqlite3ClearTempRegCache(pParse);
}
+ /* Subroutine return */
+ sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn);
+ sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1);
+ sqlite3ClearTempRegCache(pParse);
return rReg;
}
#endif /* SQLITE_OMIT_SUBQUERY */
@@ -102845,7 +104439,7 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
*/
SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){
int nVector = sqlite3ExprVectorSize(pIn->pLeft);
- if( (pIn->flags & EP_xIsSelect) ){
+ if( (pIn->flags & EP_xIsSelect)!=0 && !pParse->db->mallocFailed ){
if( nVector!=pIn->x.pSelect->pEList->nExpr ){
sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector);
return 1;
@@ -103036,6 +104630,7 @@ static void sqlite3ExprCodeIN(
if( pParse->nErr ) goto sqlite3ExprCodeIN_finished;
for(i=0; i<nVector; i++){
Expr *p = sqlite3VectorFieldSubexpr(pExpr->pLeft, i);
+ if( pParse->db->mallocFailed ) goto sqlite3ExprCodeIN_oom_error;
if( sqlite3ExprCanBeNull(p) ){
sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2);
VdbeCoverage(v);
@@ -103334,6 +104929,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse *pParse, int iFrom, int iTo, int n
*/
static void exprToRegister(Expr *pExpr, int iReg){
Expr *p = sqlite3ExprSkipCollateAndLikely(pExpr);
+ if( NEVER(p==0) ) return;
p->op2 = p->op;
p->op = TK_REGISTER;
p->iTable = iReg;
@@ -103660,7 +105256,7 @@ expr_code_doover:
** Expr node to be passed into this function, it will be handled
** sanely and not crash. But keep the assert() to bring the problem
** to the attention of the developers. */
- assert( op==TK_NULL );
+ assert( op==TK_NULL || op==TK_ERROR || pParse->db->mallocFailed );
sqlite3VdbeAddOp2(v, OP_Null, 0, target);
return target;
}
@@ -103726,8 +105322,9 @@ expr_code_doover:
}else{
r1 = sqlite3ExprCodeTemp(pParse, pLeft, &regFree1);
r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
- codeCompare(pParse, pLeft, pExpr->pRight, op,
- r1, r2, inReg, SQLITE_STOREP2 | p5,
+ sqlite3VdbeAddOp2(v, OP_Integer, 1, inReg);
+ codeCompare(pParse, pLeft, pExpr->pRight, op, r1, r2,
+ sqlite3VdbeCurrentAddr(v)+2, p5,
ExprHasProperty(pExpr,EP_Commuted));
assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt);
assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le);
@@ -103735,6 +105332,11 @@ expr_code_doover:
assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge);
assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq);
assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne);
+ if( p5==SQLITE_NULLEQ ){
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, inReg);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_ZeroOrNull, r1, inReg, r2);
+ }
testcase( regFree1==0 );
testcase( regFree2==0 );
}
@@ -103997,7 +105599,8 @@ expr_code_doover:
if( pExpr->pLeft->iTable==0 ){
pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft);
}
- assert( pExpr->iTable==0 || pExpr->pLeft->op==TK_SELECT );
+ assert( pExpr->iTable==0 || pExpr->pLeft->op==TK_SELECT
+ || pExpr->pLeft->op==TK_ERROR );
if( pExpr->iTable!=0
&& pExpr->iTable!=(n = sqlite3ExprVectorSize(pExpr->pLeft))
){
@@ -104321,6 +105924,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){
int r2;
pExpr = sqlite3ExprSkipCollateAndLikely(pExpr);
if( ConstFactorOk(pParse)
+ && ALWAYS(pExpr!=0)
&& pExpr->op!=TK_REGISTER
&& sqlite3ExprIsConstantNotJoin(pExpr)
){
@@ -105476,8 +107080,7 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){
pExpr = sqlite3ExprDup(db, pExpr, 0);
if( pExpr ){
pAggInfo->aCol[iAgg].pCExpr = pExpr;
- pParse->pConstExpr =
- sqlite3ExprListAppend(pParse, pParse->pConstExpr, pExpr);
+ sqlite3ExprDeferredDelete(pParse, pExpr);
}
}
}else{
@@ -105486,8 +107089,7 @@ static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){
pExpr = sqlite3ExprDup(db, pExpr, 0);
if( pExpr ){
pAggInfo->aFunc[iAgg].pFExpr = pExpr;
- pParse->pConstExpr =
- sqlite3ExprListAppend(pParse, pParse->pConstExpr, pExpr);
+ sqlite3ExprDeferredDelete(pParse, pExpr);
}
}
}
@@ -105559,7 +107161,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
/* Check to see if the column is in one of the tables in the FROM
** clause of the aggregate query */
if( ALWAYS(pSrcList!=0) ){
- struct SrcList_item *pItem = pSrcList->a;
+ SrcItem *pItem = pSrcList->a;
for(i=0; i<pSrcList->nSrc; i++, pItem++){
struct AggInfo_col *pCol;
assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) );
@@ -105630,6 +107232,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
*/
struct AggInfo_func *pItem = pAggInfo->aFunc;
for(i=0; i<pAggInfo->nFunc; i++, pItem++){
+ if( pItem->pFExpr==pExpr ) break;
if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){
break;
}
@@ -105830,6 +107433,7 @@ SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){
static int isAlterableTable(Parse *pParse, Table *pTab){
if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7)
#ifndef SQLITE_OMIT_VIRTUALTABLE
+ || (pTab->tabFlags & TF_Eponymous)!=0
|| ( (pTab->tabFlags & TF_Shadow)!=0
&& sqlite3ReadOnlyShadowTables(pParse->db)
)
@@ -105848,15 +107452,22 @@ static int isAlterableTable(Parse *pParse, Table *pTab){
** statement to ensure that the operation has not rendered any schema
** objects unusable.
*/
-static void renameTestSchema(Parse *pParse, const char *zDb, int bTemp){
+static void renameTestSchema(
+ Parse *pParse, /* Parse context */
+ const char *zDb, /* Name of db to verify schema of */
+ int bTemp, /* True if this is the temp db */
+ const char *zWhen, /* "when" part of error message */
+ int bNoDQS /* Do not allow DQS in the schema */
+){
+ pParse->colNamesSet = 1;
sqlite3NestedParse(pParse,
"SELECT 1 "
"FROM \"%w\"." DFLT_SCHEMA_TABLE " "
"WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'"
" AND sql NOT LIKE 'create virtual%%'"
- " AND sqlite_rename_test(%Q, sql, type, name, %d)=NULL ",
+ " AND sqlite_rename_test(%Q, sql, type, name, %d, %Q, %d)=NULL ",
zDb,
- zDb, bTemp
+ zDb, bTemp, zWhen, bNoDQS
);
if( bTemp==0 ){
@@ -105865,8 +107476,32 @@ static void renameTestSchema(Parse *pParse, const char *zDb, int bTemp){
"FROM temp." DFLT_SCHEMA_TABLE " "
"WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'"
" AND sql NOT LIKE 'create virtual%%'"
- " AND sqlite_rename_test(%Q, sql, type, name, 1)=NULL ",
- zDb
+ " AND sqlite_rename_test(%Q, sql, type, name, 1, %Q, %d)=NULL ",
+ zDb, zWhen, bNoDQS
+ );
+ }
+}
+
+/*
+** Generate VM code to replace any double-quoted strings (but not double-quoted
+** identifiers) within the "sql" column of the sqlite_schema table in
+** database zDb with their single-quoted equivalents. If argument bTemp is
+** not true, similarly update all SQL statements in the sqlite_schema table
+** of the temp db.
+*/
+static void renameFixQuotes(Parse *pParse, const char *zDb, int bTemp){
+ sqlite3NestedParse(pParse,
+ "UPDATE \"%w\"." DFLT_SCHEMA_TABLE
+ " SET sql = sqlite_rename_quotefix(%Q, sql)"
+ "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'"
+ " AND sql NOT LIKE 'create virtual%%'" , zDb, zDb
+ );
+ if( bTemp==0 ){
+ sqlite3NestedParse(pParse,
+ "UPDATE temp." DFLT_SCHEMA_TABLE
+ " SET sql = sqlite_rename_quotefix('temp', sql)"
+ "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'"
+ " AND sql NOT LIKE 'create virtual%%'"
);
}
}
@@ -105875,12 +107510,12 @@ static void renameTestSchema(Parse *pParse, const char *zDb, int bTemp){
** Generate code to reload the schema for database iDb. And, if iDb!=1, for
** the temp database as well.
*/
-static void renameReloadSchema(Parse *pParse, int iDb){
+static void renameReloadSchema(Parse *pParse, int iDb, u16 p5){
Vdbe *v = pParse->pVdbe;
if( v ){
sqlite3ChangeCookie(pParse, iDb);
- sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iDb, 0);
- if( iDb!=1 ) sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, 1, 0);
+ sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iDb, 0, p5);
+ if( iDb!=1 ) sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, 1, 0, p5);
}
}
@@ -106029,7 +107664,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable(
"sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, 1), "
"tbl_name = "
"CASE WHEN tbl_name=%Q COLLATE nocase AND "
- " sqlite_rename_test(%Q, sql, type, name, 1) "
+ " sqlite_rename_test(%Q, sql, type, name, 1, 'after rename', 0) "
"THEN %Q ELSE tbl_name END "
"WHERE type IN ('view', 'trigger')"
, zDb, zTabName, zName, zTabName, zDb, zName);
@@ -106048,8 +107683,8 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable(
}
#endif
- renameReloadSchema(pParse, iDb);
- renameTestSchema(pParse, zDb, iDb==1);
+ renameReloadSchema(pParse, iDb, INITFLAG_AlterRename);
+ renameTestSchema(pParse, zDb, iDb==1, "after rename", 0);
exit_rename_table:
sqlite3SrcListDelete(db, pSrc);
@@ -106180,11 +107815,14 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){
*zEnd-- = '\0';
}
db->mDbFlags |= DBFLAG_PreferBuiltin;
+ /* substr() operations on characters, but addColOffset is in bytes. So we
+ ** have to use printf() to translate between these units: */
sqlite3NestedParse(pParse,
"UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET "
- "sql = substr(sql,1,%d) || ', ' || %Q || substr(sql,%d) "
+ "sql = printf('%%.%ds, ',sql) || %Q"
+ " || substr(sql,1+length(printf('%%.%ds',sql))) "
"WHERE type = 'table' AND name = %Q",
- zDb, pNew->addColOffset, zCol, pNew->addColOffset+1,
+ zDb, pNew->addColOffset, zCol, pNew->addColOffset,
zTab
);
sqlite3DbFree(db, zCol);
@@ -106208,7 +107846,7 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){
}
/* Reload the table definition */
- renameReloadSchema(pParse, iDb);
+ renameReloadSchema(pParse, iDb, INITFLAG_AlterRename);
}
/*
@@ -106308,7 +107946,7 @@ exit_begin_add_column:
** Or, if pTab is not a view or virtual table, zero is returned.
*/
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE)
-static int isRealTable(Parse *pParse, Table *pTab){
+static int isRealTable(Parse *pParse, Table *pTab, int bDrop){
const char *zType = 0;
#ifndef SQLITE_OMIT_VIEW
if( pTab->pSelect ){
@@ -106321,15 +107959,16 @@ static int isRealTable(Parse *pParse, Table *pTab){
}
#endif
if( zType ){
- sqlite3ErrorMsg(
- pParse, "cannot rename columns of %s \"%s\"", zType, pTab->zName
+ sqlite3ErrorMsg(pParse, "cannot %s %s \"%s\"",
+ (bDrop ? "drop column from" : "rename columns of"),
+ zType, pTab->zName
);
return 1;
}
return 0;
}
#else /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */
-# define isRealTable(x,y) (0)
+# define isRealTable(x,y,z) (0)
#endif
/*
@@ -106358,7 +107997,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn(
/* Cannot alter a system table */
if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_rename_column;
- if( SQLITE_OK!=isRealTable(pParse, pTab) ) goto exit_rename_column;
+ if( SQLITE_OK!=isRealTable(pParse, pTab, 0) ) goto exit_rename_column;
/* Which schema holds the table to be altered */
iSchema = sqlite3SchemaToIndex(db, pTab->pSchema);
@@ -106384,6 +108023,10 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn(
goto exit_rename_column;
}
+ /* Ensure the schema contains no double-quoted strings */
+ renameTestSchema(pParse, zDb, iSchema==1, "", 0);
+ renameFixQuotes(pParse, zDb, iSchema==1);
+
/* Do the rename operation using a recursive UPDATE statement that
** uses the sqlite_rename_column() SQL function to compute the new
** CREATE statement text for the sqlite_schema table.
@@ -106412,8 +108055,8 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn(
);
/* Drop and reload the database schema. */
- renameReloadSchema(pParse, iSchema);
- renameTestSchema(pParse, zDb, iSchema==1);
+ renameReloadSchema(pParse, iSchema, INITFLAG_AlterRename);
+ renameTestSchema(pParse, zDb, iSchema==1, "after rename", 1);
exit_rename_column:
sqlite3SrcListDelete(db, pSrc);
@@ -106559,15 +108202,30 @@ static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){
static void renameWalkWith(Walker *pWalker, Select *pSelect){
With *pWith = pSelect->pWith;
if( pWith ){
+ Parse *pParse = pWalker->pParse;
int i;
+ With *pCopy = 0;
+ assert( pWith->nCte>0 );
+ if( (pWith->a[0].pSelect->selFlags & SF_Expanded)==0 ){
+ /* Push a copy of the With object onto the with-stack. We use a copy
+ ** here as the original will be expanded and resolved (flags SF_Expanded
+ ** and SF_Resolved) below. And the parser code that uses the with-stack
+ ** fails if the Select objects on it have already been expanded and
+ ** resolved. */
+ pCopy = sqlite3WithDup(pParse->db, pWith);
+ pCopy = sqlite3WithPush(pParse, pCopy, 1);
+ }
for(i=0; i<pWith->nCte; i++){
Select *p = pWith->a[i].pSelect;
NameContext sNC;
memset(&sNC, 0, sizeof(sNC));
- sNC.pParse = pWalker->pParse;
- sqlite3SelectPrep(sNC.pParse, p, &sNC);
+ sNC.pParse = pParse;
+ if( pCopy ) sqlite3SelectPrep(sNC.pParse, p, &sNC);
sqlite3WalkSelect(pWalker, p);
- sqlite3RenameExprlistUnmap(pWalker->pParse, pWith->a[i].pCols);
+ sqlite3RenameExprlistUnmap(pParse, pWith->a[i].pCols);
+ }
+ if( pCopy && pParse->pWith==pCopy ){
+ pParse->pWith = pCopy->pOuter;
}
}
}
@@ -106594,7 +108252,11 @@ static int renameUnmapSelectCb(Walker *pWalker, Select *p){
Parse *pParse = pWalker->pParse;
int i;
if( pParse->nErr ) return WRC_Abort;
- if( NEVER(p->selFlags & SF_View) ) return WRC_Prune;
+ if( p->selFlags & (SF_View|SF_CopyCte) ){
+ testcase( p->selFlags & SF_View );
+ testcase( p->selFlags & SF_CopyCte );
+ return WRC_Prune;
+ }
if( ALWAYS(p->pEList) ){
ExprList *pList = p->pEList;
for(i=0; i<pList->nExpr; i++){
@@ -106665,23 +108327,35 @@ static void renameTokenFree(sqlite3 *db, RenameToken *pToken){
/*
** Search the Parse object passed as the first argument for a RenameToken
-** object associated with parse tree element pPtr. If found, remove it
-** from the Parse object and add it to the list maintained by the
-** RenameCtx object passed as the second argument.
+** object associated with parse tree element pPtr. If found, return a pointer
+** to it. Otherwise, return NULL.
+**
+** If the second argument passed to this function is not NULL and a matching
+** RenameToken object is found, remove it from the Parse object and add it to
+** the list maintained by the RenameCtx object.
*/
-static void renameTokenFind(Parse *pParse, struct RenameCtx *pCtx, void *pPtr){
+static RenameToken *renameTokenFind(
+ Parse *pParse,
+ struct RenameCtx *pCtx,
+ void *pPtr
+){
RenameToken **pp;
- assert( pPtr!=0 );
+ if( NEVER(pPtr==0) ){
+ return 0;
+ }
for(pp=&pParse->pRename; (*pp); pp=&(*pp)->pNext){
if( (*pp)->p==pPtr ){
RenameToken *pToken = *pp;
- *pp = pToken->pNext;
- pToken->pNext = pCtx->pList;
- pCtx->pList = pToken;
- pCtx->nList++;
- break;
+ if( pCtx ){
+ *pp = pToken->pNext;
+ pToken->pNext = pCtx->pList;
+ pCtx->pList = pToken;
+ pCtx->nList++;
+ }
+ return pToken;
}
}
+ return 0;
}
/*
@@ -106690,7 +108364,11 @@ static void renameTokenFind(Parse *pParse, struct RenameCtx *pCtx, void *pPtr){
** descend into sub-select statements.
*/
static int renameColumnSelectCb(Walker *pWalker, Select *p){
- if( p->selFlags & SF_View ) return WRC_Prune;
+ if( p->selFlags & (SF_View|SF_CopyCte) ){
+ testcase( p->selFlags & SF_View );
+ testcase( p->selFlags & SF_CopyCte );
+ return WRC_Prune;
+ }
renameWalkWith(pWalker, p);
return WRC_Continue;
}
@@ -106752,7 +108430,7 @@ static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){
*/
static void renameColumnParseError(
sqlite3_context *pCtx,
- int bPost,
+ const char *zWhen,
sqlite3_value *pType,
sqlite3_value *pObject,
Parse *pParse
@@ -106761,8 +108439,8 @@ static void renameColumnParseError(
const char *zN = (const char*)sqlite3_value_text(pObject);
char *zErr;
- zErr = sqlite3_mprintf("error in %s %s%s: %s",
- zT, zN, (bPost ? " after rename" : ""),
+ zErr = sqlite3_mprintf("error in %s %s%s%s: %s",
+ zT, zN, (zWhen[0] ? " " : ""), zWhen,
pParse->zErrMsg
);
sqlite3_result_error(pCtx, zErr, -1);
@@ -106841,7 +108519,7 @@ static int renameParseSql(
p->eParseMode = PARSE_MODE_RENAME;
p->db = db;
p->nQueryLoop = 1;
- rc = sqlite3RunParser(p, zSql, &zErr);
+ rc = zSql ? sqlite3RunParser(p, zSql, &zErr) : SQLITE_NOMEM;
assert( p->zErrMsg==0 );
assert( rc!=SQLITE_OK || zErr==0 );
p->zErrMsg = zErr;
@@ -106884,51 +108562,76 @@ static int renameEditSql(
const char *zNew, /* New token text */
int bQuote /* True to always quote token */
){
- int nNew = sqlite3Strlen30(zNew);
- int nSql = sqlite3Strlen30(zSql);
+ i64 nNew = sqlite3Strlen30(zNew);
+ i64 nSql = sqlite3Strlen30(zSql);
sqlite3 *db = sqlite3_context_db_handle(pCtx);
int rc = SQLITE_OK;
- char *zQuot;
+ char *zQuot = 0;
char *zOut;
- int nQuot;
-
- /* Set zQuot to point to a buffer containing a quoted copy of the
- ** identifier zNew. If the corresponding identifier in the original
- ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to
- ** point to zQuot so that all substitutions are made using the
- ** quoted version of the new column name. */
- zQuot = sqlite3MPrintf(db, "\"%w\"", zNew);
- if( zQuot==0 ){
- return SQLITE_NOMEM;
+ i64 nQuot = 0;
+ char *zBuf1 = 0;
+ char *zBuf2 = 0;
+
+ if( zNew ){
+ /* Set zQuot to point to a buffer containing a quoted copy of the
+ ** identifier zNew. If the corresponding identifier in the original
+ ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to
+ ** point to zQuot so that all substitutions are made using the
+ ** quoted version of the new column name. */
+ zQuot = sqlite3MPrintf(db, "\"%w\" ", zNew);
+ if( zQuot==0 ){
+ return SQLITE_NOMEM;
+ }else{
+ nQuot = sqlite3Strlen30(zQuot)-1;
+ }
+
+ assert( nQuot>=nNew );
+ zOut = sqlite3DbMallocZero(db, nSql + pRename->nList*nQuot + 1);
}else{
- nQuot = sqlite3Strlen30(zQuot);
- }
- if( bQuote ){
- zNew = zQuot;
- nNew = nQuot;
+ zOut = (char*)sqlite3DbMallocZero(db, (nSql*2+1) * 3);
+ if( zOut ){
+ zBuf1 = &zOut[nSql*2+1];
+ zBuf2 = &zOut[nSql*4+2];
+ }
}
/* At this point pRename->pList contains a list of RenameToken objects
** corresponding to all tokens in the input SQL that must be replaced
- ** with the new column name. All that remains is to construct and
- ** return the edited SQL string. */
- assert( nQuot>=nNew );
- zOut = sqlite3DbMallocZero(db, nSql + pRename->nList*nQuot + 1);
+ ** with the new column name, or with single-quoted versions of themselves.
+ ** All that remains is to construct and return the edited SQL string. */
if( zOut ){
int nOut = nSql;
memcpy(zOut, zSql, nSql);
while( pRename->pList ){
int iOff; /* Offset of token to replace in zOut */
- RenameToken *pBest = renameColumnTokenNext(pRename);
-
u32 nReplace;
const char *zReplace;
- if( sqlite3IsIdChar(*pBest->t.z) ){
- nReplace = nNew;
- zReplace = zNew;
+ RenameToken *pBest = renameColumnTokenNext(pRename);
+
+ if( zNew ){
+ if( bQuote==0 && sqlite3IsIdChar(*pBest->t.z) ){
+ nReplace = nNew;
+ zReplace = zNew;
+ }else{
+ nReplace = nQuot;
+ zReplace = zQuot;
+ if( pBest->t.z[pBest->t.n]=='"' ) nReplace++;
+ }
}else{
- nReplace = nQuot;
- zReplace = zQuot;
+ /* Dequote the double-quoted token. Then requote it again, this time
+ ** using single quotes. If the character immediately following the
+ ** original token within the input SQL was a single quote ('), then
+ ** add another space after the new, single-quoted version of the
+ ** token. This is so that (SELECT "string"'alias') maps to
+ ** (SELECT 'string' 'alias'), and not (SELECT 'string''alias'). */
+ memcpy(zBuf1, pBest->t.z, pBest->t.n);
+ zBuf1[pBest->t.n] = 0;
+ sqlite3Dequote(zBuf1);
+ sqlite3_snprintf(nSql*2, zBuf2, "%Q%s", zBuf1,
+ pBest->t.z[pBest->t.n]=='\'' ? " " : ""
+ );
+ zReplace = zBuf2;
+ nReplace = sqlite3Strlen30(zReplace);
}
iOff = pBest->t.z - zSql;
@@ -106994,14 +108697,22 @@ static int renameResolveTrigger(Parse *pParse){
if( pSrc ){
int i;
for(i=0; i<pSrc->nSrc && rc==SQLITE_OK; i++){
- struct SrcList_item *p = &pSrc->a[i];
- p->pTab = sqlite3LocateTableItem(pParse, 0, p);
+ SrcItem *p = &pSrc->a[i];
p->iCursor = pParse->nTab++;
- if( p->pTab==0 ){
- rc = SQLITE_ERROR;
+ if( p->pSelect ){
+ sqlite3SelectPrep(pParse, p->pSelect, 0);
+ sqlite3ExpandSubquery(pParse, p);
+ assert( i>0 );
+ assert( pStep->pFrom->a[i-1].pSelect );
+ sqlite3SelectPrep(pParse, pStep->pFrom->a[i-1].pSelect, 0);
}else{
- p->pTab->nTabRef++;
- rc = sqlite3ViewGetColumnNames(pParse, p->pTab);
+ p->pTab = sqlite3LocateTableItem(pParse, 0, p);
+ if( p->pTab==0 ){
+ rc = SQLITE_ERROR;
+ }else{
+ p->pTab->nTabRef++;
+ rc = sqlite3ViewGetColumnNames(pParse, p->pTab);
+ }
}
}
sNC.pSrcList = pSrc;
@@ -107012,9 +108723,8 @@ static int renameResolveTrigger(Parse *pParse){
rc = sqlite3ResolveExprListNames(&sNC, pStep->pExprList);
}
assert( !pStep->pUpsert || (!pStep->pWhere && !pStep->pExprList) );
- if( pStep->pUpsert ){
+ if( pStep->pUpsert && rc==SQLITE_OK ){
Upsert *pUpsert = pStep->pUpsert;
- assert( rc==SQLITE_OK );
pUpsert->pUpsertSrc = pSrc;
sNC.uNC.pUpsert = pUpsert;
sNC.ncFlags = NC_UUpsert;
@@ -107063,6 +108773,12 @@ static void renameWalkTrigger(Walker *pWalker, Trigger *pTrigger){
sqlite3WalkExpr(pWalker, pUpsert->pUpsertWhere);
sqlite3WalkExpr(pWalker, pUpsert->pUpsertTargetWhere);
}
+ if( pStep->pFrom ){
+ int i;
+ for(i=0; i<pStep->pFrom->nSrc; i++){
+ sqlite3WalkSelect(pWalker, pStep->pFrom->a[i].pSelect);
+ }
+ }
}
}
@@ -107182,9 +108898,11 @@ static void renameColumnFunc(
assert( sParse.pNewTable->pSelect==0 );
sCtx.pTab = sParse.pNewTable;
if( bFKOnly==0 ){
- renameTokenFind(
- &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zName
- );
+ if( iCol<sParse.pNewTable->nCol ){
+ renameTokenFind(
+ &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zName
+ );
+ }
if( sCtx.iCol<0 ){
renameTokenFind(&sParse, &sCtx, (void*)&sParse.pNewTable->iPKey);
}
@@ -107195,12 +108913,12 @@ static void renameColumnFunc(
for(pIdx=sParse.pNewIndex; pIdx; pIdx=pIdx->pNext){
sqlite3WalkExprList(&sWalker, pIdx->aColExpr);
}
- }
#ifndef SQLITE_OMIT_GENERATED_COLUMNS
- for(i=0; i<sParse.pNewTable->nCol; i++){
- sqlite3WalkExpr(&sWalker, sParse.pNewTable->aCol[i].pDflt);
- }
+ for(i=0; i<sParse.pNewTable->nCol; i++){
+ sqlite3WalkExpr(&sWalker, sParse.pNewTable->aCol[i].pDflt);
+ }
#endif
+ }
for(pFKey=sParse.pNewTable->pFKey; pFKey; pFKey=pFKey->pNextFrom){
for(i=0; i<pFKey->nCol; i++){
@@ -107254,7 +108972,7 @@ static void renameColumnFunc(
renameColumnFunc_done:
if( rc!=SQLITE_OK ){
if( sParse.zErrMsg ){
- renameColumnParseError(context, 0, argv[1], argv[2], &sParse);
+ renameColumnParseError(context, "", argv[1], argv[2], &sParse);
}else{
sqlite3_result_error_code(context, rc);
}
@@ -107286,13 +109004,17 @@ static int renameTableSelectCb(Walker *pWalker, Select *pSelect){
int i;
RenameCtx *p = pWalker->u.pRename;
SrcList *pSrc = pSelect->pSrc;
- if( pSelect->selFlags & SF_View ) return WRC_Prune;
- if( pSrc==0 ){
+ if( pSelect->selFlags & (SF_View|SF_CopyCte) ){
+ testcase( pSelect->selFlags & SF_View );
+ testcase( pSelect->selFlags & SF_CopyCte );
+ return WRC_Prune;
+ }
+ if( NEVER(pSrc==0) ){
assert( pWalker->pParse->db->mallocFailed );
return WRC_Abort;
}
for(i=0; i<pSrc->nSrc; i++){
- struct SrcList_item *pItem = &pSrc->a[i];
+ SrcItem *pItem = &pSrc->a[i];
if( pItem->pTab==p->pTab ){
renameTokenFind(pWalker->pParse, p, pItem->zName);
}
@@ -107443,7 +109165,7 @@ static void renameTableFunc(
}
if( rc!=SQLITE_OK ){
if( sParse.zErrMsg ){
- renameColumnParseError(context, 0, argv[1], argv[2], &sParse);
+ renameColumnParseError(context, "", argv[1], argv[2], &sParse);
}else{
sqlite3_result_error_code(context, rc);
}
@@ -107460,6 +109182,119 @@ static void renameTableFunc(
return;
}
+static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){
+ if( pExpr->op==TK_STRING && (pExpr->flags & EP_DblQuoted) ){
+ renameTokenFind(pWalker->pParse, pWalker->u.pRename, (void*)pExpr);
+ }
+ return WRC_Continue;
+}
+
+/*
+** The implementation of an SQL scalar function that rewrites DDL statements
+** so that any string literals that use double-quotes are modified so that
+** they use single quotes.
+**
+** Two arguments must be passed:
+**
+** 0: Database name ("main", "temp" etc.).
+** 1: SQL statement to edit.
+**
+** The returned value is the modified SQL statement. For example, given
+** the database schema:
+**
+** CREATE TABLE t1(a, b, c);
+**
+** SELECT sqlite_rename_quotefix('main',
+** 'CREATE VIEW v1 AS SELECT "a", "string" FROM t1'
+** );
+**
+** returns the string:
+**
+** CREATE VIEW v1 AS SELECT "a", 'string' FROM t1
+*/
+static void renameQuotefixFunc(
+ sqlite3_context *context,
+ int NotUsed,
+ sqlite3_value **argv
+){
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ char const *zDb = (const char*)sqlite3_value_text(argv[0]);
+ char const *zInput = (const char*)sqlite3_value_text(argv[1]);
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ sqlite3_xauth xAuth = db->xAuth;
+ db->xAuth = 0;
+#endif
+
+ sqlite3BtreeEnterAll(db);
+
+ UNUSED_PARAMETER(NotUsed);
+ if( zDb && zInput ){
+ int rc;
+ Parse sParse;
+ rc = renameParseSql(&sParse, zDb, db, zInput, 0);
+
+ if( rc==SQLITE_OK ){
+ RenameCtx sCtx;
+ Walker sWalker;
+
+ /* Walker to find tokens that need to be replaced. */
+ memset(&sCtx, 0, sizeof(RenameCtx));
+ memset(&sWalker, 0, sizeof(Walker));
+ sWalker.pParse = &sParse;
+ sWalker.xExprCallback = renameQuotefixExprCb;
+ sWalker.xSelectCallback = renameColumnSelectCb;
+ sWalker.u.pRename = &sCtx;
+
+ if( sParse.pNewTable ){
+ Select *pSelect = sParse.pNewTable->pSelect;
+ if( pSelect ){
+ pSelect->selFlags &= ~SF_View;
+ sParse.rc = SQLITE_OK;
+ sqlite3SelectPrep(&sParse, pSelect, 0);
+ rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc);
+ if( rc==SQLITE_OK ){
+ sqlite3WalkSelect(&sWalker, pSelect);
+ }
+ }else{
+ int i;
+ sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck);
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ for(i=0; i<sParse.pNewTable->nCol; i++){
+ sqlite3WalkExpr(&sWalker, sParse.pNewTable->aCol[i].pDflt);
+ }
+#endif /* SQLITE_OMIT_GENERATED_COLUMNS */
+ }
+ }else if( sParse.pNewIndex ){
+ sqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr);
+ sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere);
+ }else{
+#ifndef SQLITE_OMIT_TRIGGER
+ rc = renameResolveTrigger(&sParse);
+ if( rc==SQLITE_OK ){
+ renameWalkTrigger(&sWalker, sParse.pNewTrigger);
+ }
+#endif /* SQLITE_OMIT_TRIGGER */
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = renameEditSql(context, &sCtx, zInput, 0, 0);
+ }
+ renameTokenFree(db, sCtx.pList);
+ }
+ if( rc!=SQLITE_OK ){
+ sqlite3_result_error_code(context, rc);
+ }
+ renameParseCleanup(&sParse);
+ }
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ db->xAuth = xAuth;
+#endif
+
+ sqlite3BtreeLeaveAll(db);
+}
+
/*
** An SQL user function that checks that there are no parse or symbol
** resolution problems in a CREATE TRIGGER|TABLE|VIEW|INDEX statement.
@@ -107472,6 +109307,8 @@ static void renameTableFunc(
** 2: Object type ("view", "table", "trigger" or "index").
** 3: Object name.
** 4: True if object is from temp schema.
+** 5: "when" part of error message.
+** 6: True to disable the DQS quirk when parsing SQL.
**
** Unless it finds an error, this function normally returns NULL. However, it
** returns integer value 1 if:
@@ -107489,6 +109326,8 @@ static void renameTableTest(
char const *zInput = (const char*)sqlite3_value_text(argv[1]);
int bTemp = sqlite3_value_int(argv[4]);
int isLegacy = (db->flags & SQLITE_LegacyAlter);
+ char const *zWhen = (const char*)sqlite3_value_text(argv[5]);
+ int bNoDQS = sqlite3_value_int(argv[6]);
#ifndef SQLITE_OMIT_AUTHORIZATION
sqlite3_xauth xAuth = db->xAuth;
@@ -107496,10 +109335,14 @@ static void renameTableTest(
#endif
UNUSED_PARAMETER(NotUsed);
+
if( zDb && zInput ){
int rc;
Parse sParse;
+ int flags = db->flags;
+ if( bNoDQS ) db->flags &= ~(SQLITE_DqsDML|SQLITE_DqsDDL);
rc = renameParseSql(&sParse, zDb, db, zInput, bTemp);
+ db->flags |= (flags & (SQLITE_DqsDML|SQLITE_DqsDDL));
if( rc==SQLITE_OK ){
if( isLegacy==0 && sParse.pNewTable && sParse.pNewTable->pSelect ){
NameContext sNC;
@@ -107521,8 +109364,8 @@ static void renameTableTest(
}
}
- if( rc!=SQLITE_OK ){
- renameColumnParseError(context, 1, argv[2], argv[3], &sParse);
+ if( rc!=SQLITE_OK && zWhen ){
+ renameColumnParseError(context, zWhen, argv[2], argv[3],&sParse);
}
renameParseCleanup(&sParse);
}
@@ -107533,13 +109376,218 @@ static void renameTableTest(
}
/*
+** The implementation of internal UDF sqlite_drop_column().
+**
+** Arguments:
+**
+** argv[0]: An integer - the index of the schema containing the table
+** argv[1]: CREATE TABLE statement to modify.
+** argv[2]: An integer - the index of the column to remove.
+**
+** The value returned is a string containing the CREATE TABLE statement
+** with column argv[2] removed.
+*/
+static void dropColumnFunc(
+ sqlite3_context *context,
+ int NotUsed,
+ sqlite3_value **argv
+){
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ int iSchema = sqlite3_value_int(argv[0]);
+ const char *zSql = (const char*)sqlite3_value_text(argv[1]);
+ int iCol = sqlite3_value_int(argv[2]);
+ const char *zDb = db->aDb[iSchema].zDbSName;
+ int rc;
+ Parse sParse;
+ RenameToken *pCol;
+ Table *pTab;
+ const char *zEnd;
+ char *zNew = 0;
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ sqlite3_xauth xAuth = db->xAuth;
+ db->xAuth = 0;
+#endif
+
+ UNUSED_PARAMETER(NotUsed);
+ rc = renameParseSql(&sParse, zDb, db, zSql, iSchema==1);
+ if( rc!=SQLITE_OK ) goto drop_column_done;
+ pTab = sParse.pNewTable;
+ if( pTab==0 || pTab->nCol==1 || iCol>=pTab->nCol ){
+ /* This can happen if the sqlite_schema table is corrupt */
+ rc = SQLITE_CORRUPT_BKPT;
+ goto drop_column_done;
+ }
+
+ pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zName);
+ if( iCol<pTab->nCol-1 ){
+ RenameToken *pEnd;
+ pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zName);
+ zEnd = (const char*)pEnd->t.z;
+ }else{
+ zEnd = (const char*)&zSql[pTab->addColOffset];
+ while( ALWAYS(pCol->t.z[0]!=0) && pCol->t.z[0]!=',' ) pCol->t.z--;
+ }
+
+ zNew = sqlite3MPrintf(db, "%.*s%s", pCol->t.z-zSql, zSql, zEnd);
+ sqlite3_result_text(context, zNew, -1, SQLITE_TRANSIENT);
+ sqlite3_free(zNew);
+
+drop_column_done:
+ renameParseCleanup(&sParse);
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ db->xAuth = xAuth;
+#endif
+ if( rc!=SQLITE_OK ){
+ sqlite3_result_error_code(context, rc);
+ }
+}
+
+/*
+** This function is called by the parser upon parsing an
+**
+** ALTER TABLE pSrc DROP COLUMN pName
+**
+** statement. Argument pSrc contains the possibly qualified name of the
+** table being edited, and token pName the name of the column to drop.
+*/
+SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token *pName){
+ sqlite3 *db = pParse->db; /* Database handle */
+ Table *pTab; /* Table to modify */
+ int iDb; /* Index of db containing pTab in aDb[] */
+ const char *zDb; /* Database containing pTab ("main" etc.) */
+ char *zCol = 0; /* Name of column to drop */
+ int iCol; /* Index of column zCol in pTab->aCol[] */
+
+ /* Look up the table being altered. */
+ assert( pParse->pNewTable==0 );
+ assert( sqlite3BtreeHoldsAllMutexes(db) );
+ if( NEVER(db->mallocFailed) ) goto exit_drop_column;
+ pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]);
+ if( !pTab ) goto exit_drop_column;
+
+ /* Make sure this is not an attempt to ALTER a view, virtual table or
+ ** system table. */
+ if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_drop_column;
+ if( SQLITE_OK!=isRealTable(pParse, pTab, 1) ) goto exit_drop_column;
+
+ /* Find the index of the column being dropped. */
+ zCol = sqlite3NameFromToken(db, pName);
+ if( zCol==0 ){
+ assert( db->mallocFailed );
+ goto exit_drop_column;
+ }
+ iCol = sqlite3ColumnIndex(pTab, zCol);
+ if( iCol<0 ){
+ sqlite3ErrorMsg(pParse, "no such column: \"%s\"", zCol);
+ goto exit_drop_column;
+ }
+
+ /* Do not allow the user to drop a PRIMARY KEY column or a column
+ ** constrained by a UNIQUE constraint. */
+ if( pTab->aCol[iCol].colFlags & (COLFLAG_PRIMKEY|COLFLAG_UNIQUE) ){
+ sqlite3ErrorMsg(pParse, "cannot drop %s column: \"%s\"",
+ (pTab->aCol[iCol].colFlags&COLFLAG_PRIMKEY) ? "PRIMARY KEY" : "UNIQUE",
+ zCol
+ );
+ goto exit_drop_column;
+ }
+
+ /* Do not allow the number of columns to go to zero */
+ if( pTab->nCol<=1 ){
+ sqlite3ErrorMsg(pParse, "cannot drop column \"%s\": no other columns exist",zCol);
+ goto exit_drop_column;
+ }
+
+ /* Edit the sqlite_schema table */
+ iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ assert( iDb>=0 );
+ zDb = db->aDb[iDb].zDbSName;
+ renameTestSchema(pParse, zDb, iDb==1, "", 0);
+ renameFixQuotes(pParse, zDb, iDb==1);
+ sqlite3NestedParse(pParse,
+ "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET "
+ "sql = sqlite_drop_column(%d, sql, %d) "
+ "WHERE (type=='table' AND tbl_name=%Q COLLATE nocase)"
+ , zDb, iDb, iCol, pTab->zName
+ );
+
+ /* Drop and reload the database schema. */
+ renameReloadSchema(pParse, iDb, INITFLAG_AlterDrop);
+ renameTestSchema(pParse, zDb, iDb==1, "after drop column", 1);
+
+ /* Edit rows of table on disk */
+ if( pParse->nErr==0 && (pTab->aCol[iCol].colFlags & COLFLAG_VIRTUAL)==0 ){
+ int i;
+ int addr;
+ int reg;
+ int regRec;
+ Index *pPk = 0;
+ int nField = 0; /* Number of non-virtual columns after drop */
+ int iCur;
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ iCur = pParse->nTab++;
+ sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenWrite);
+ addr = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v);
+ reg = ++pParse->nMem;
+ if( HasRowid(pTab) ){
+ sqlite3VdbeAddOp2(v, OP_Rowid, iCur, reg);
+ pParse->nMem += pTab->nCol;
+ }else{
+ pPk = sqlite3PrimaryKeyIndex(pTab);
+ pParse->nMem += pPk->nColumn;
+ for(i=0; i<pPk->nKeyCol; i++){
+ sqlite3VdbeAddOp3(v, OP_Column, iCur, i, reg+i+1);
+ }
+ nField = pPk->nKeyCol;
+ }
+ regRec = ++pParse->nMem;
+ for(i=0; i<pTab->nCol; i++){
+ if( i!=iCol && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){
+ int regOut;
+ if( pPk ){
+ int iPos = sqlite3TableColumnToIndex(pPk, i);
+ int iColPos = sqlite3TableColumnToIndex(pPk, iCol);
+ if( iPos<pPk->nKeyCol ) continue;
+ regOut = reg+1+iPos-(iPos>iColPos);
+ }else{
+ regOut = reg+1+nField;
+ }
+ if( i==pTab->iPKey ){
+ sqlite3VdbeAddOp2(v, OP_Null, 0, regOut);
+ }else{
+ sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOut);
+ }
+ nField++;
+ }
+ }
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, reg+1, nField, regRec);
+ if( pPk ){
+ sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iCur, regRec, reg+1, pPk->nKeyCol);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_Insert, iCur, regRec, reg);
+ }
+ sqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION);
+
+ sqlite3VdbeAddOp2(v, OP_Next, iCur, addr+1); VdbeCoverage(v);
+ sqlite3VdbeJumpHere(v, addr);
+ }
+
+exit_drop_column:
+ sqlite3DbFree(db, zCol);
+ sqlite3SrcListDelete(db, pSrc);
+}
+
+/*
** Register built-in functions used to help implement ALTER TABLE
*/
SQLITE_PRIVATE void sqlite3AlterFunctions(void){
static FuncDef aAlterTableFuncs[] = {
- INTERNAL_FUNCTION(sqlite_rename_column, 9, renameColumnFunc),
- INTERNAL_FUNCTION(sqlite_rename_table, 7, renameTableFunc),
- INTERNAL_FUNCTION(sqlite_rename_test, 5, renameTableTest),
+ INTERNAL_FUNCTION(sqlite_rename_column, 9, renameColumnFunc),
+ INTERNAL_FUNCTION(sqlite_rename_table, 7, renameTableFunc),
+ INTERNAL_FUNCTION(sqlite_rename_test, 7, renameTableTest),
+ INTERNAL_FUNCTION(sqlite_drop_column, 3, dropColumnFunc),
+ INTERNAL_FUNCTION(sqlite_rename_quotefix,2, renameQuotefixFunc),
};
sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs));
}
@@ -109321,6 +111369,7 @@ static int loadStatTbl(
}
pSpace = (tRowcnt*)&pIdx->aSample[nSample];
pIdx->aAvgEq = pSpace; pSpace += nIdxCol;
+ pIdx->pTable->tabFlags |= TF_HasStat4;
for(i=0; i<nSample; i++){
pIdx->aSample[i].anEq = pSpace; pSpace += nIdxCol;
pIdx->aSample[i].anLt = pSpace; pSpace += nIdxCol;
@@ -109589,7 +111638,7 @@ static void attachFunc(
if( zFile==0 ) zFile = "";
if( zName==0 ) zName = "";
-#ifdef SQLITE_ENABLE_DESERIALIZE
+#ifndef SQLITE_OMIT_DESERIALIZE
# define REOPEN_AS_MEMDB(db) (db->init.reopenMemdb)
#else
# define REOPEN_AS_MEMDB(db) (0)
@@ -109787,7 +111836,9 @@ static void detachFunc(
sqlite3_snprintf(sizeof(zErr),zErr, "cannot detach database %s", zName);
goto detach_error;
}
- if( sqlite3BtreeIsInReadTrans(pDb->pBt) || sqlite3BtreeIsInBackup(pDb->pBt) ){
+ if( sqlite3BtreeTxnState(pDb->pBt)!=SQLITE_TXN_NONE
+ || sqlite3BtreeIsInBackup(pDb->pBt)
+ ){
sqlite3_snprintf(sizeof(zErr),zErr, "database %s is locked", zName);
goto detach_error;
}
@@ -109926,6 +111977,65 @@ SQLITE_PRIVATE void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *p
#endif /* SQLITE_OMIT_ATTACH */
/*
+** Expression callback used by sqlite3FixAAAA() routines.
+*/
+static int fixExprCb(Walker *p, Expr *pExpr){
+ DbFixer *pFix = p->u.pFix;
+ if( !pFix->bTemp ) ExprSetProperty(pExpr, EP_FromDDL);
+ if( pExpr->op==TK_VARIABLE ){
+ if( pFix->pParse->db->init.busy ){
+ pExpr->op = TK_NULL;
+ }else{
+ sqlite3ErrorMsg(pFix->pParse, "%s cannot use variables", pFix->zType);
+ return WRC_Abort;
+ }
+ }
+ return WRC_Continue;
+}
+
+/*
+** Select callback used by sqlite3FixAAAA() routines.
+*/
+static int fixSelectCb(Walker *p, Select *pSelect){
+ DbFixer *pFix = p->u.pFix;
+ int i;
+ SrcItem *pItem;
+ sqlite3 *db = pFix->pParse->db;
+ int iDb = sqlite3FindDbName(db, pFix->zDb);
+ SrcList *pList = pSelect->pSrc;
+
+ if( NEVER(pList==0) ) return WRC_Continue;
+ for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){
+ if( pFix->bTemp==0 ){
+ if( pItem->zDatabase ){
+ if( iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){
+ sqlite3ErrorMsg(pFix->pParse,
+ "%s %T cannot reference objects in database %s",
+ pFix->zType, pFix->pName, pItem->zDatabase);
+ return WRC_Abort;
+ }
+ sqlite3DbFree(db, pItem->zDatabase);
+ pItem->zDatabase = 0;
+ pItem->fg.notCte = 1;
+ }
+ pItem->pSchema = pFix->pSchema;
+ pItem->fg.fromDDL = 1;
+ }
+#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
+ if( sqlite3WalkExpr(&pFix->w, pList->a[i].pOn) ) return WRC_Abort;
+#endif
+ }
+ if( pSelect->pWith ){
+ for(i=0; i<pSelect->pWith->nCte; i++){
+ if( sqlite3WalkSelect(p, pSelect->pWith->a[i].pSelect) ){
+ return WRC_Abort;
+ }
+ }
+ }
+ return WRC_Continue;
+}
+
+/*
** Initialize a DbFixer structure. This routine must be called prior
** to passing the structure to one of the sqliteFixAAAA() routines below.
*/
@@ -109936,9 +112046,7 @@ SQLITE_PRIVATE void sqlite3FixInit(
const char *zType, /* "view", "trigger", or "index" */
const Token *pName /* Name of the view, trigger, or index */
){
- sqlite3 *db;
-
- db = pParse->db;
+ sqlite3 *db = pParse->db;
assert( db->nDb>iDb );
pFix->pParse = pParse;
pFix->zDb = db->aDb[iDb].zDbSName;
@@ -109946,6 +112054,13 @@ SQLITE_PRIVATE void sqlite3FixInit(
pFix->zType = zType;
pFix->pName = pName;
pFix->bTemp = (iDb==1);
+ pFix->w.pParse = pParse;
+ pFix->w.xExprCallback = fixExprCb;
+ pFix->w.xSelectCallback = fixSelectCb;
+ pFix->w.xSelectCallback2 = sqlite3WalkWinDefnDummyCallback;
+ pFix->w.walkerDepth = 0;
+ pFix->w.eCode = 0;
+ pFix->w.u.pFix = pFix;
}
/*
@@ -109966,115 +112081,27 @@ SQLITE_PRIVATE int sqlite3FixSrcList(
DbFixer *pFix, /* Context of the fixation */
SrcList *pList /* The Source list to check and modify */
){
- int i;
- struct SrcList_item *pItem;
- sqlite3 *db = pFix->pParse->db;
- int iDb = sqlite3FindDbName(db, pFix->zDb);
-
- if( NEVER(pList==0) ) return 0;
-
- for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){
- if( pFix->bTemp==0 ){
- if( pItem->zDatabase && iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){
- sqlite3ErrorMsg(pFix->pParse,
- "%s %T cannot reference objects in database %s",
- pFix->zType, pFix->pName, pItem->zDatabase);
- return 1;
- }
- sqlite3DbFree(db, pItem->zDatabase);
- pItem->zDatabase = 0;
- pItem->pSchema = pFix->pSchema;
- pItem->fg.fromDDL = 1;
- }
-#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
- if( sqlite3FixSelect(pFix, pItem->pSelect) ) return 1;
- if( sqlite3FixExpr(pFix, pItem->pOn) ) return 1;
-#endif
- if( pItem->fg.isTabFunc && sqlite3FixExprList(pFix, pItem->u1.pFuncArg) ){
- return 1;
- }
+ int res = 0;
+ if( pList ){
+ Select s;
+ memset(&s, 0, sizeof(s));
+ s.pSrc = pList;
+ res = sqlite3WalkSelect(&pFix->w, &s);
}
- return 0;
+ return res;
}
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
SQLITE_PRIVATE int sqlite3FixSelect(
DbFixer *pFix, /* Context of the fixation */
Select *pSelect /* The SELECT statement to be fixed to one database */
){
- while( pSelect ){
- if( sqlite3FixExprList(pFix, pSelect->pEList) ){
- return 1;
- }
- if( sqlite3FixSrcList(pFix, pSelect->pSrc) ){
- return 1;
- }
- if( sqlite3FixExpr(pFix, pSelect->pWhere) ){
- return 1;
- }
- if( sqlite3FixExprList(pFix, pSelect->pGroupBy) ){
- return 1;
- }
- if( sqlite3FixExpr(pFix, pSelect->pHaving) ){
- return 1;
- }
- if( sqlite3FixExprList(pFix, pSelect->pOrderBy) ){
- return 1;
- }
- if( sqlite3FixExpr(pFix, pSelect->pLimit) ){
- return 1;
- }
- if( pSelect->pWith ){
- int i;
- for(i=0; i<pSelect->pWith->nCte; i++){
- if( sqlite3FixSelect(pFix, pSelect->pWith->a[i].pSelect) ){
- return 1;
- }
- }
- }
- pSelect = pSelect->pPrior;
- }
- return 0;
+ return sqlite3WalkSelect(&pFix->w, pSelect);
}
SQLITE_PRIVATE int sqlite3FixExpr(
DbFixer *pFix, /* Context of the fixation */
Expr *pExpr /* The expression to be fixed to one database */
){
- while( pExpr ){
- if( !pFix->bTemp ) ExprSetProperty(pExpr, EP_FromDDL);
- if( pExpr->op==TK_VARIABLE ){
- if( pFix->pParse->db->init.busy ){
- pExpr->op = TK_NULL;
- }else{
- sqlite3ErrorMsg(pFix->pParse, "%s cannot use variables", pFix->zType);
- return 1;
- }
- }
- if( ExprHasProperty(pExpr, EP_TokenOnly|EP_Leaf) ) break;
- if( ExprHasProperty(pExpr, EP_xIsSelect) ){
- if( sqlite3FixSelect(pFix, pExpr->x.pSelect) ) return 1;
- }else{
- if( sqlite3FixExprList(pFix, pExpr->x.pList) ) return 1;
- }
- if( sqlite3FixExpr(pFix, pExpr->pRight) ){
- return 1;
- }
- pExpr = pExpr->pLeft;
- }
- return 0;
-}
-SQLITE_PRIVATE int sqlite3FixExprList(
- DbFixer *pFix, /* Context of the fixation */
- ExprList *pList /* The expression to be fixed to one database */
-){
- int i;
- struct ExprList_item *pItem;
- if( pList==0 ) return 0;
- for(i=0, pItem=pList->a; i<pList->nExpr; i++, pItem++){
- if( sqlite3FixExpr(pFix, pItem->pExpr) ){
- return 1;
- }
- }
- return 0;
+ return sqlite3WalkExpr(&pFix->w, pExpr);
}
#endif
@@ -110084,32 +112111,30 @@ SQLITE_PRIVATE int sqlite3FixTriggerStep(
TriggerStep *pStep /* The trigger step be fixed to one database */
){
while( pStep ){
- if( sqlite3FixSelect(pFix, pStep->pSelect) ){
- return 1;
- }
- if( sqlite3FixExpr(pFix, pStep->pWhere) ){
- return 1;
- }
- if( sqlite3FixExprList(pFix, pStep->pExprList) ){
- return 1;
- }
- if( pStep->pFrom && sqlite3FixSrcList(pFix, pStep->pFrom) ){
+ if( sqlite3WalkSelect(&pFix->w, pStep->pSelect)
+ || sqlite3WalkExpr(&pFix->w, pStep->pWhere)
+ || sqlite3WalkExprList(&pFix->w, pStep->pExprList)
+ || sqlite3FixSrcList(pFix, pStep->pFrom)
+ ){
return 1;
}
#ifndef SQLITE_OMIT_UPSERT
- if( pStep->pUpsert ){
- Upsert *pUp = pStep->pUpsert;
- if( sqlite3FixExprList(pFix, pUp->pUpsertTarget)
- || sqlite3FixExpr(pFix, pUp->pUpsertTargetWhere)
- || sqlite3FixExprList(pFix, pUp->pUpsertSet)
- || sqlite3FixExpr(pFix, pUp->pUpsertWhere)
- ){
- return 1;
+ {
+ Upsert *pUp;
+ for(pUp=pStep->pUpsert; pUp; pUp=pUp->pNextUpsert){
+ if( sqlite3WalkExprList(&pFix->w, pUp->pUpsertTarget)
+ || sqlite3WalkExpr(&pFix->w, pUp->pUpsertTargetWhere)
+ || sqlite3WalkExprList(&pFix->w, pUp->pUpsertSet)
+ || sqlite3WalkExpr(&pFix->w, pUp->pUpsertWhere)
+ ){
+ return 1;
+ }
}
}
#endif
pStep = pStep->pNext;
}
+
return 0;
}
#endif
@@ -110261,7 +112286,6 @@ SQLITE_PRIVATE void sqlite3AuthRead(
Schema *pSchema, /* The schema of the expression */
SrcList *pTabList /* All table that pExpr might refer to */
){
- sqlite3 *db = pParse->db;
Table *pTab = 0; /* The table being read */
const char *zCol; /* Name of the column of the table */
int iSrc; /* Index in pTabList->a[] of table being read */
@@ -110269,8 +112293,8 @@ SQLITE_PRIVATE void sqlite3AuthRead(
int iCol; /* Index of column in table */
assert( pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER );
- assert( !IN_RENAME_OBJECT || db->xAuth==0 );
- if( db->xAuth==0 ) return;
+ assert( !IN_RENAME_OBJECT );
+ assert( pParse->db->xAuth!=0 );
iDb = sqlite3SchemaToIndex(pParse->db, pSchema);
if( iDb<0 ){
/* An attempt to read a column out of a subquery or other
@@ -110282,7 +112306,7 @@ SQLITE_PRIVATE void sqlite3AuthRead(
pTab = pParse->pTriggerTab;
}else{
assert( pTabList );
- for(iSrc=0; ALWAYS(iSrc<pTabList->nSrc); iSrc++){
+ for(iSrc=0; iSrc<pTabList->nSrc; iSrc++){
if( pExpr->iTable==pTabList->a[iSrc].iCursor ){
pTab = pTabList->a[iSrc].pTab;
break;
@@ -110290,7 +112314,7 @@ SQLITE_PRIVATE void sqlite3AuthRead(
}
}
iCol = pExpr->iColumn;
- if( NEVER(pTab==0) ) return;
+ if( pTab==0 ) return;
if( iCol>=0 ){
assert( iCol<pTab->nCol );
@@ -110301,7 +112325,7 @@ SQLITE_PRIVATE void sqlite3AuthRead(
}else{
zCol = "ROWID";
}
- assert( iDb>=0 && iDb<db->nDb );
+ assert( iDb>=0 && iDb<pParse->db->nDb );
if( SQLITE_IGNORE==sqlite3AuthReadCol(pParse, pTab->zName, zCol, iDb) ){
pExpr->op = TK_NULL;
}
@@ -110327,11 +112351,7 @@ SQLITE_PRIVATE int sqlite3AuthCheck(
** or if the parser is being invoked from within sqlite3_declare_vtab.
*/
assert( !IN_RENAME_OBJECT || db->xAuth==0 );
- if( db->init.busy || IN_SPECIAL_PARSE ){
- return SQLITE_OK;
- }
-
- if( db->xAuth==0 ){
+ if( db->xAuth==0 || db->init.busy || IN_SPECIAL_PARSE ){
return SQLITE_OK;
}
@@ -110440,21 +112460,20 @@ struct TableLock {
** code to make the lock occur is generated by a later call to
** codeTableLocks() which occurs during sqlite3FinishCoding().
*/
-SQLITE_PRIVATE void sqlite3TableLock(
+static SQLITE_NOINLINE void lockTable(
Parse *pParse, /* Parsing context */
int iDb, /* Index of the database containing the table to lock */
Pgno iTab, /* Root page number of the table to be locked */
u8 isWriteLock, /* True for a write lock */
const char *zName /* Name of the table to be locked */
){
- Parse *pToplevel = sqlite3ParseToplevel(pParse);
+ Parse *pToplevel;
int i;
int nBytes;
TableLock *p;
assert( iDb>=0 );
- if( iDb==1 ) return;
- if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return;
+ pToplevel = sqlite3ParseToplevel(pParse);
for(i=0; i<pToplevel->nTableLock; i++){
p = &pToplevel->aTableLock[i];
if( p->iDb==iDb && p->iTab==iTab ){
@@ -110477,6 +112496,17 @@ SQLITE_PRIVATE void sqlite3TableLock(
sqlite3OomFault(pToplevel->db);
}
}
+SQLITE_PRIVATE void sqlite3TableLock(
+ Parse *pParse, /* Parsing context */
+ int iDb, /* Index of the database containing the table to lock */
+ Pgno iTab, /* Root page number of the table to be locked */
+ u8 isWriteLock, /* True for a write lock */
+ const char *zName /* Name of the table to be locked */
+){
+ if( iDb==1 ) return;
+ if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return;
+ lockTable(pParse, iDb, iTab, isWriteLock, zName);
+}
/*
** Code an OP_TableLock instruction for each table locked by the
@@ -110484,10 +112514,8 @@ SQLITE_PRIVATE void sqlite3TableLock(
*/
static void codeTableLocks(Parse *pParse){
int i;
- Vdbe *pVdbe;
-
- pVdbe = sqlite3GetVdbe(pParse);
- assert( pVdbe!=0 ); /* sqlite3GetVdbe cannot fail: VDBE already allocated */
+ Vdbe *pVdbe = pParse->pVdbe;
+ assert( pVdbe!=0 );
for(i=0; i<pParse->nTableLock; i++){
TableLock *p = &pParse->aTableLock[i];
@@ -110538,10 +112566,36 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
/* Begin by generating some termination code at the end of the
** vdbe program
*/
- v = sqlite3GetVdbe(pParse);
+ v = pParse->pVdbe;
+ if( v==0 ){
+ if( db->init.busy ){
+ pParse->rc = SQLITE_DONE;
+ return;
+ }
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) pParse->rc = SQLITE_ERROR;
+ }
assert( !pParse->isMultiWrite
|| sqlite3VdbeAssertMayAbort(v, pParse->mayAbort));
if( v ){
+ if( pParse->bReturning ){
+ Returning *pReturning = pParse->u1.pReturning;
+ int addrRewind;
+ int i;
+ int reg;
+
+ addrRewind =
+ sqlite3VdbeAddOp1(v, OP_Rewind, pReturning->iRetCur);
+ VdbeCoverage(v);
+ reg = pReturning->iRetReg;
+ for(i=0; i<pReturning->nRetCol; i++){
+ sqlite3VdbeAddOp3(v, OP_Column, pReturning->iRetCur, i, reg+i);
+ }
+ sqlite3VdbeAddOp2(v, OP_ResultRow, reg, i);
+ sqlite3VdbeAddOp2(v, OP_Next, pReturning->iRetCur, addrRewind+1);
+ VdbeCoverage(v);
+ sqlite3VdbeJumpHere(v, addrRewind);
+ }
sqlite3VdbeAddOp0(v, OP_Halt);
#if SQLITE_USER_AUTHENTICATION
@@ -110619,12 +112673,16 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
}
}
+ if( pParse->bReturning ){
+ Returning *pRet = pParse->u1.pReturning;
+ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol);
+ }
+
/* Finally, jump back to the beginning of the executable code. */
sqlite3VdbeGoto(v, 1);
}
}
-
/* Get the VDBE program ready for execution
*/
if( v && pParse->nErr==0 && !db->mallocFailed ){
@@ -110803,7 +112861,7 @@ SQLITE_PRIVATE Table *sqlite3LocateTable(
/* If zName is the not the name of a table in the schema created using
** CREATE, then check to see if it is the name of an virtual table that
** can be an eponymous virtual table. */
- if( pParse->disableVtab==0 ){
+ if( pParse->disableVtab==0 && db->init.busy==0 ){
Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName);
if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){
pMod = sqlite3PragmaVtabRegister(db, zName);
@@ -110826,6 +112884,8 @@ SQLITE_PRIVATE Table *sqlite3LocateTable(
}else{
sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName);
}
+ }else{
+ assert( HasRowid(p) || p->iPKey<0 );
}
return p;
@@ -110843,7 +112903,7 @@ SQLITE_PRIVATE Table *sqlite3LocateTable(
SQLITE_PRIVATE Table *sqlite3LocateTableItem(
Parse *pParse,
u32 flags,
- struct SrcList_item *p
+ SrcItem *p
){
const char *zDb;
assert( p->pSchema==0 || p->zDatabase==0 );
@@ -111242,7 +113302,7 @@ SQLITE_PRIVATE int sqlite3TwoPartName(
return -1;
}
}else{
- assert( db->init.iDb==0 || db->init.busy || IN_RENAME_OBJECT
+ assert( db->init.iDb==0 || db->init.busy || IN_SPECIAL_PARSE
|| (db->mDbFlags & DBFLAG_Vacuum)!=0);
iDb = db->init.iDb;
*pUnqual = pName1;
@@ -111412,6 +113472,23 @@ SQLITE_PRIVATE i16 sqlite3TableColumnToStorage(Table *pTab, i16 iCol){
#endif
/*
+** Insert a single OP_JournalMode query opcode in order to force the
+** prepared statement to return false for sqlite3_stmt_readonly(). This
+** is used by CREATE TABLE IF NOT EXISTS and similar if the table already
+** exists, so that the prepared statement for CREATE TABLE IF NOT EXISTS
+** will return false for sqlite3_stmt_readonly() even if that statement
+** is a read-only no-op.
+*/
+static void sqlite3ForceNotReadOnly(Parse *pParse){
+ int iReg = ++pParse->nMem;
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp3(v, OP_JournalMode, 0, iReg, PAGER_JOURNALMODE_QUERY);
+ sqlite3VdbeUsesBtree(v, 0);
+ }
+}
+
+/*
** Begin constructing a new table representation in memory. This is
** the first of several action routines that get called in response
** to a CREATE TABLE statement. In particular, this routine is called
@@ -111510,6 +113587,7 @@ SQLITE_PRIVATE void sqlite3StartTable(
}else{
assert( !db->init.busy || CORRUPT_DB );
sqlite3CodeVerifySchema(pParse, iDb);
+ sqlite3ForceNotReadOnly(pParse);
}
goto begin_table_error;
}
@@ -111538,17 +113616,6 @@ SQLITE_PRIVATE void sqlite3StartTable(
assert( pParse->pNewTable==0 );
pParse->pNewTable = pTable;
- /* If this is the magic sqlite_sequence table used by autoincrement,
- ** then record a pointer to this table in the main database structure
- ** so that INSERT can find the table easily.
- */
-#ifndef SQLITE_OMIT_AUTOINCREMENT
- if( !pParse->nested && strcmp(zName, "sqlite_sequence")==0 ){
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
- pTable->pSchema->pSeqTab = pTable;
- }
-#endif
-
/* Begin generating the code that will insert the table record into
** the schema table. Note in particular that we must go ahead
** and allocate the record number for the table entry now. Before any
@@ -111601,7 +113668,8 @@ SQLITE_PRIVATE void sqlite3StartTable(
}else
#endif
{
- pParse->addrCrTab =
+ assert( !pParse->bReturning );
+ pParse->u1.addrCrTab =
sqlite3VdbeAddOp3(v, OP_CreateBtree, iDb, reg2, BTREE_INTKEY);
}
sqlite3OpenSchemaTable(pParse, iDb);
@@ -111628,12 +113696,86 @@ begin_table_error:
SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){
if( sqlite3_strnicmp(pCol->zName, "__hidden__", 10)==0 ){
pCol->colFlags |= COLFLAG_HIDDEN;
+ if( pTab ) pTab->tabFlags |= TF_HasHidden;
}else if( pTab && pCol!=pTab->aCol && (pCol[-1].colFlags & COLFLAG_HIDDEN) ){
pTab->tabFlags |= TF_OOOHidden;
}
}
#endif
+/*
+** Name of the special TEMP trigger used to implement RETURNING. The
+** name begins with "sqlite_" so that it is guaranteed not to collide
+** with any application-generated triggers.
+*/
+#define RETURNING_TRIGGER_NAME "sqlite_returning"
+
+/*
+** Clean up the data structures associated with the RETURNING clause.
+*/
+static void sqlite3DeleteReturning(sqlite3 *db, Returning *pRet){
+ Hash *pHash;
+ pHash = &(db->aDb[1].pSchema->trigHash);
+ sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, 0);
+ sqlite3ExprListDelete(db, pRet->pReturnEL);
+ sqlite3DbFree(db, pRet);
+}
+
+/*
+** Add the RETURNING clause to the parse currently underway.
+**
+** This routine creates a special TEMP trigger that will fire for each row
+** of the DML statement. That TEMP trigger contains a single SELECT
+** statement with a result set that is the argument of the RETURNING clause.
+** The trigger has the Trigger.bReturning flag and an opcode of
+** TK_RETURNING instead of TK_SELECT, so that the trigger code generator
+** knows to handle it specially. The TEMP trigger is automatically
+** removed at the end of the parse.
+**
+** When this routine is called, we do not yet know if the RETURNING clause
+** is attached to a DELETE, INSERT, or UPDATE, so construct it as a
+** RETURNING trigger instead. It will then be converted into the appropriate
+** type on the first call to sqlite3TriggersExist().
+*/
+SQLITE_PRIVATE void sqlite3AddReturning(Parse *pParse, ExprList *pList){
+ Returning *pRet;
+ Hash *pHash;
+ sqlite3 *db = pParse->db;
+ if( pParse->pNewTrigger ){
+ sqlite3ErrorMsg(pParse, "cannot use RETURNING in a trigger");
+ }else{
+ assert( pParse->bReturning==0 );
+ }
+ pParse->bReturning = 1;
+ pRet = sqlite3DbMallocZero(db, sizeof(*pRet));
+ if( pRet==0 ){
+ sqlite3ExprListDelete(db, pList);
+ return;
+ }
+ pParse->u1.pReturning = pRet;
+ pRet->pParse = pParse;
+ pRet->pReturnEL = pList;
+ sqlite3ParserAddCleanup(pParse,
+ (void(*)(sqlite3*,void*))sqlite3DeleteReturning, pRet);
+ testcase( pParse->earlyCleanup );
+ if( db->mallocFailed ) return;
+ pRet->retTrig.zName = RETURNING_TRIGGER_NAME;
+ pRet->retTrig.op = TK_RETURNING;
+ pRet->retTrig.tr_tm = TRIGGER_AFTER;
+ pRet->retTrig.bReturning = 1;
+ pRet->retTrig.pSchema = db->aDb[1].pSchema;
+ pRet->retTrig.pTabSchema = db->aDb[1].pSchema;
+ pRet->retTrig.step_list = &pRet->retTStep;
+ pRet->retTStep.op = TK_RETURNING;
+ pRet->retTStep.pTrig = &pRet->retTrig;
+ pRet->retTStep.pExprList = pList;
+ pHash = &(db->aDb[1].pSchema->trigHash);
+ assert( sqlite3HashFind(pHash, RETURNING_TRIGGER_NAME)==0 || pParse->nErr );
+ if( sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, &pRet->retTrig)
+ ==&pRet->retTrig ){
+ sqlite3OomFault(db);
+ }
+}
/*
** Add a new column to the table currently being constructed.
@@ -111650,6 +113792,8 @@ SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
char *zType;
Column *pCol;
sqlite3 *db = pParse->db;
+ u8 hName;
+
if( (p = pParse->pNewTable)==0 ) return;
if( p->nCol+1>db->aLimit[SQLITE_LIMIT_COLUMN] ){
sqlite3ErrorMsg(pParse, "too many columns on %s", p->zName);
@@ -111661,8 +113805,9 @@ SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
memcpy(z, pName->z, pName->n);
z[pName->n] = 0;
sqlite3Dequote(z);
+ hName = sqlite3StrIHash(z);
for(i=0; i<p->nCol; i++){
- if( sqlite3_stricmp(z, p->aCol[i].zName)==0 ){
+ if( p->aCol[i].hName==hName && sqlite3StrICmp(z, p->aCol[i].zName)==0 ){
sqlite3ErrorMsg(pParse, "duplicate column name: %s", z);
sqlite3DbFree(db, z);
return;
@@ -111680,7 +113825,7 @@ SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
pCol = &p->aCol[p->nCol];
memset(pCol, 0, sizeof(p->aCol[0]));
pCol->zName = z;
- pCol->hName = sqlite3StrIHash(z);
+ pCol->hName = hName;
sqlite3ColumnPropertiesFromName(p, pCol);
if( pType->n==0 ){
@@ -112012,8 +114157,10 @@ primary_key_exit:
** Add a new CHECK constraint to the table currently under construction.
*/
SQLITE_PRIVATE void sqlite3AddCheckConstraint(
- Parse *pParse, /* Parsing context */
- Expr *pCheckExpr /* The check expression */
+ Parse *pParse, /* Parsing context */
+ Expr *pCheckExpr, /* The check expression */
+ const char *zStart, /* Opening "(" */
+ const char *zEnd /* Closing ")" */
){
#ifndef SQLITE_OMIT_CHECK
Table *pTab = pParse->pNewTable;
@@ -112024,6 +114171,13 @@ SQLITE_PRIVATE void sqlite3AddCheckConstraint(
pTab->pCheck = sqlite3ExprListAppend(pParse, pTab->pCheck, pCheckExpr);
if( pParse->constraintName.n ){
sqlite3ExprListSetName(pParse, pTab->pCheck, &pParse->constraintName, 1);
+ }else{
+ Token t;
+ for(zStart++; sqlite3Isspace(zStart[0]); zStart++){}
+ while( sqlite3Isspace(zEnd[-1]) ){ zEnd--; }
+ t.z = zStart;
+ t.n = (int)(zEnd - t.z);
+ sqlite3ExprListSetName(pParse, pTab->pCheck, &t, 1);
}
}else
#endif
@@ -112042,7 +114196,7 @@ SQLITE_PRIVATE void sqlite3AddCollateType(Parse *pParse, Token *pToken){
char *zColl; /* Dequoted name of collation sequence */
sqlite3 *db;
- if( (p = pParse->pNewTable)==0 ) return;
+ if( (p = pParse->pNewTable)==0 || IN_RENAME_OBJECT ) return;
i = p->nCol-1;
db = pParse->db;
zColl = sqlite3NameFromToken(db, pToken);
@@ -112277,12 +114431,15 @@ static int resizeIndexObject(sqlite3 *db, Index *pIdx, int N){
int nByte;
if( pIdx->nColumn>=N ) return SQLITE_OK;
assert( pIdx->isResized==0 );
- nByte = (sizeof(char*) + sizeof(i16) + 1)*N;
+ nByte = (sizeof(char*) + sizeof(LogEst) + sizeof(i16) + 1)*N;
zExtra = sqlite3DbMallocZero(db, nByte);
if( zExtra==0 ) return SQLITE_NOMEM_BKPT;
memcpy(zExtra, pIdx->azColl, sizeof(char*)*pIdx->nColumn);
pIdx->azColl = (const char**)zExtra;
zExtra += sizeof(char*)*N;
+ memcpy(zExtra, pIdx->aiRowLogEst, sizeof(LogEst)*(pIdx->nKeyCol+1));
+ pIdx->aiRowLogEst = (LogEst*)zExtra;
+ zExtra += sizeof(LogEst)*N;
memcpy(zExtra, pIdx->aiColumn, sizeof(i16)*pIdx->nColumn);
pIdx->aiColumn = (i16*)zExtra;
zExtra += sizeof(i16)*N;
@@ -112451,9 +114608,10 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
/* Convert the P3 operand of the OP_CreateBtree opcode from BTREE_INTKEY
** into BTREE_BLOBKEY.
*/
- if( pParse->addrCrTab ){
+ assert( !pParse->bReturning );
+ if( pParse->u1.addrCrTab ){
assert( v );
- sqlite3VdbeChangeP3(v, pParse->addrCrTab, BTREE_BLOBKEY);
+ sqlite3VdbeChangeP3(v, pParse->u1.addrCrTab, BTREE_BLOBKEY);
}
/* Locate the PRIMARY KEY index. Or, if this table was originally
@@ -112465,7 +114623,10 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zName);
pList = sqlite3ExprListAppend(pParse, 0,
sqlite3ExprAlloc(db, TK_ID, &ipkToken, 0));
- if( pList==0 ) return;
+ if( pList==0 ){
+ pTab->tabFlags &= ~TF_WithoutRowid;
+ return;
+ }
if( IN_RENAME_OBJECT ){
sqlite3RenameTokenRemap(pParse, pList->a[0].pExpr, &pTab->iPKey);
}
@@ -112474,7 +114635,10 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
pTab->iPKey = -1;
sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0,
SQLITE_IDXTYPE_PRIMARYKEY);
- if( db->mallocFailed || pParse->nErr ) return;
+ if( db->mallocFailed || pParse->nErr ){
+ pTab->tabFlags &= ~TF_WithoutRowid;
+ return;
+ }
pPk = sqlite3PrimaryKeyIndex(pTab);
assert( pPk->nKeyCol==1 );
}else{
@@ -112678,7 +114842,6 @@ SQLITE_PRIVATE void sqlite3EndTable(
if( pEnd==0 && pSelect==0 ){
return;
}
- assert( !db->mallocFailed );
p = pParse->pNewTable;
if( p==0 ) return;
@@ -112903,7 +115066,7 @@ SQLITE_PRIVATE void sqlite3EndTable(
/* Check to see if we need to create an sqlite_sequence table for
** keeping track of autoincrement keys.
*/
- if( (p->tabFlags & TF_Autoincrement)!=0 ){
+ if( (p->tabFlags & TF_Autoincrement)!=0 && !IN_SPECIAL_PARSE ){
Db *pDb = &db->aDb[iDb];
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
if( pDb->pSchema->pSeqTab==0 ){
@@ -112917,7 +115080,7 @@ SQLITE_PRIVATE void sqlite3EndTable(
/* Reparse everything to update our internal data structures */
sqlite3VdbeAddParseSchemaOp(v, iDb,
- sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName));
+ sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName),0);
}
/* Add the table to the in-memory representation of the database.
@@ -112926,6 +115089,7 @@ SQLITE_PRIVATE void sqlite3EndTable(
Table *pOld;
Schema *pSchema = p->pSchema;
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( HasRowid(p) || p->iPKey<0 );
pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, p);
if( pOld ){
assert( p==pOld ); /* Malloc must have failed inside HashInsert() */
@@ -112935,19 +115099,27 @@ SQLITE_PRIVATE void sqlite3EndTable(
pParse->pNewTable = 0;
db->mDbFlags |= DBFLAG_SchemaChange;
-#ifndef SQLITE_OMIT_ALTERTABLE
- if( !p->pSelect ){
- const char *zName = (const char *)pParse->sNameToken.z;
- int nName;
- assert( !pSelect && pCons && pEnd );
- if( pCons->z==0 ){
- pCons = pEnd;
- }
- nName = (int)((const char *)pCons->z - zName);
- p->addColOffset = 13 + sqlite3Utf8CharLen(zName, nName);
+ /* If this is the magic sqlite_sequence table used by autoincrement,
+ ** then record a pointer to this table in the main database structure
+ ** so that INSERT can find the table easily. */
+ assert( !pParse->nested );
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ if( strcmp(p->zName, "sqlite_sequence")==0 ){
+ assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ p->pSchema->pSeqTab = p;
}
#endif
}
+
+#ifndef SQLITE_OMIT_ALTERTABLE
+ if( !pSelect && !p->pSelect ){
+ assert( pCons && pEnd );
+ if( pCons->z==0 ){
+ pCons = pEnd;
+ }
+ p->addColOffset = 13 + (int)(pCons->z - pParse->sNameToken.z);
+ }
+#endif
}
#ifndef SQLITE_OMIT_VIEW
@@ -112980,6 +115152,16 @@ SQLITE_PRIVATE void sqlite3CreateView(
sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr);
p = pParse->pNewTable;
if( p==0 || pParse->nErr ) goto create_view_fail;
+
+ /* Legacy versions of SQLite allowed the use of the magic "rowid" column
+ ** on a view, even though views do not have rowids. The following flag
+ ** setting fixes this problem. But the fix can be disabled by compiling
+ ** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that
+ ** depend upon the old buggy behavior. */
+#ifndef SQLITE_ALLOW_ROWID_IN_VIEW
+ p->tabFlags |= TF_NoVisibleRowid;
+#endif
+
sqlite3TwoPartName(pParse, pName1, pName2, &pName);
iDb = sqlite3SchemaToIndex(db, p->pSchema);
sqlite3FixInit(&sFix, pParse, iDb, "view", pName);
@@ -113138,6 +115320,7 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
assert( pTable->aCol==0 );
pTable->nCol = pSelTab->nCol;
pTable->aCol = pSelTab->aCol;
+ pTable->tabFlags |= (pSelTab->tabFlags & COLFLAG_NOINSERT);
pSelTab->nCol = 0;
pSelTab->aCol = 0;
assert( sqlite3SchemaMutexHeld(db, 0, pTable->pSchema) );
@@ -113455,7 +115638,10 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView,
if( noErr ) db->suppressErr--;
if( pTab==0 ){
- if( noErr ) sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
+ if( noErr ){
+ sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
+ sqlite3ForceNotReadOnly(pParse);
+ }
goto exit_drop_table;
}
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
@@ -114025,6 +116211,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
}else{
assert( !db->init.busy );
sqlite3CodeVerifySchema(pParse, iDb);
+ sqlite3ForceNotReadOnly(pParse);
}
goto exit_create_index;
}
@@ -114405,7 +116592,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
sqlite3RefillIndex(pParse, pIndex, iMem);
sqlite3ChangeCookie(pParse, iDb);
sqlite3VdbeAddParseSchemaOp(v, iDb,
- sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName));
+ sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName), 0);
sqlite3VdbeAddOp2(v, OP_Expire, 0, 1);
}
@@ -114426,7 +116613,11 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
/* Clean up before exiting */
exit_create_index:
if( pIndex ) sqlite3FreeIndex(db, pIndex);
- if( pTab ){ /* Ensure all REPLACE indexes are at the end of the list */
+ if( pTab ){
+ /* Ensure all REPLACE indexes on pTab are at the end of the pIndex list.
+ ** The list was already ordered when this routine was entered, so at this
+ ** point at most a single index (the newly added index) will be out of
+ ** order. So we have to reorder at most one index. */
Index **ppFrom = &pTab->pIndex;
Index *pThis;
for(ppFrom=&pTab->pIndex; (pThis = *ppFrom)!=0; ppFrom=&pThis->pNext){
@@ -114440,6 +116631,16 @@ exit_create_index:
}
break;
}
+#ifdef SQLITE_DEBUG
+ /* Verify that all REPLACE indexes really are now at the end
+ ** of the index list. In other words, no other index type ever
+ ** comes after a REPLACE index on the list. */
+ for(pThis = pTab->pIndex; pThis; pThis=pThis->pNext){
+ assert( pThis->onError!=OE_Replace
+ || pThis->pNext==0
+ || pThis->pNext->onError==OE_Replace );
+ }
+#endif
}
sqlite3ExprDelete(db, pPIWhere);
sqlite3ExprListDelete(db, pList);
@@ -114491,7 +116692,7 @@ SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){
if( x<99 ){
pIdx->pTable->nRowLogEst = x = 99;
}
- if( pIdx->pPartIdxWhere!=0 ) x -= 10; assert( 10==sqlite3LogEst(2) );
+ if( pIdx->pPartIdxWhere!=0 ){ x -= 10; assert( 10==sqlite3LogEst(2) ); }
a[0] = x;
/* Estimate that a[1] is 10, a[2] is 9, a[3] is 8, a[4] is 7, a[5] is
@@ -114526,9 +116727,10 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists
pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase);
if( pIndex==0 ){
if( !ifExists ){
- sqlite3ErrorMsg(pParse, "no such index: %S", pName, 0);
+ sqlite3ErrorMsg(pParse, "no such index: %S", pName->a);
}else{
sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
+ sqlite3ForceNotReadOnly(pParse);
}
pParse->checkSchema = 1;
goto exit_drop_index;
@@ -114548,7 +116750,7 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists
if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
goto exit_drop_index;
}
- if( !OMIT_TEMPDB && iDb ) code = SQLITE_DROP_TEMP_INDEX;
+ if( !OMIT_TEMPDB && iDb==1 ) code = SQLITE_DROP_TEMP_INDEX;
if( sqlite3AuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){
goto exit_drop_index;
}
@@ -114798,7 +117000,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(
Token *pTable, /* Table to append */
Token *pDatabase /* Database of the table */
){
- struct SrcList_item *pItem;
+ SrcItem *pItem;
sqlite3 *db;
assert( pDatabase==0 || pTable!=0 ); /* Cannot have C without B */
assert( pParse!=0 );
@@ -114839,11 +117041,11 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(
*/
SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){
int i;
- struct SrcList_item *pItem;
- assert(pList || pParse->db->mallocFailed );
- if( pList ){
+ SrcItem *pItem;
+ assert( pList || pParse->db->mallocFailed );
+ if( ALWAYS(pList) ){
for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){
- if( pItem->iCursor>=0 ) break;
+ if( pItem->iCursor>=0 ) continue;
pItem->iCursor = pParse->nTab++;
if( pItem->pSelect ){
sqlite3SrcListAssignCursors(pParse, pItem->pSelect->pSrc);
@@ -114857,18 +117059,18 @@ SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){
*/
SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){
int i;
- struct SrcList_item *pItem;
+ SrcItem *pItem;
if( pList==0 ) return;
for(pItem=pList->a, i=0; i<pList->nSrc; i++, pItem++){
- sqlite3DbFree(db, pItem->zDatabase);
+ if( pItem->zDatabase ) sqlite3DbFreeNN(db, pItem->zDatabase);
sqlite3DbFree(db, pItem->zName);
- sqlite3DbFree(db, pItem->zAlias);
+ if( pItem->zAlias ) sqlite3DbFreeNN(db, pItem->zAlias);
if( pItem->fg.isIndexedBy ) sqlite3DbFree(db, pItem->u1.zIndexedBy);
if( pItem->fg.isTabFunc ) sqlite3ExprListDelete(db, pItem->u1.pFuncArg);
sqlite3DeleteTable(db, pItem->pTab);
- sqlite3SelectDelete(db, pItem->pSelect);
- sqlite3ExprDelete(db, pItem->pOn);
- sqlite3IdListDelete(db, pItem->pUsing);
+ if( pItem->pSelect ) sqlite3SelectDelete(db, pItem->pSelect);
+ if( pItem->pOn ) sqlite3ExprDelete(db, pItem->pOn);
+ if( pItem->pUsing ) sqlite3IdListDelete(db, pItem->pUsing);
}
sqlite3DbFreeNN(db, pList);
}
@@ -114899,7 +117101,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(
Expr *pOn, /* The ON clause of a join */
IdList *pUsing /* The USING clause of a join */
){
- struct SrcList_item *pItem;
+ SrcItem *pItem;
sqlite3 *db = pParse->db;
if( !p && (pOn || pUsing) ){
sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s",
@@ -114943,7 +117145,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(
SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){
assert( pIndexedBy!=0 );
if( p && pIndexedBy->n>0 ){
- struct SrcList_item *pItem;
+ SrcItem *pItem;
assert( p->nSrc>0 );
pItem = &p->a[p->nSrc-1];
assert( pItem->fg.notIndexed==0 );
@@ -114973,7 +117175,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, Src
sqlite3SrcListDelete(pParse->db, p2);
}else{
p1 = pNew;
- memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(struct SrcList_item));
+ memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(SrcItem));
sqlite3DbFree(pParse->db, p2);
}
}
@@ -114986,7 +117188,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, Src
*/
SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *pList){
if( p ){
- struct SrcList_item *pItem = &p->a[p->nSrc-1];
+ SrcItem *pItem = &p->a[p->nSrc-1];
assert( pItem->fg.notIndexed==0 );
assert( pItem->fg.isIndexedBy==0 );
assert( pItem->fg.isTabFunc==0 );
@@ -115040,7 +117242,16 @@ SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){
if( !v ) return;
if( type!=TK_DEFERRED ){
for(i=0; i<db->nDb; i++){
- sqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1);
+ int eTxnType;
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt && sqlite3BtreeIsReadonly(pBt) ){
+ eTxnType = 0; /* Read txn */
+ }else if( type==TK_EXCLUSIVE ){
+ eTxnType = 2; /* Exclusive txn */
+ }else{
+ eTxnType = 1; /* Write txn */
+ }
+ sqlite3VdbeAddOp2(v, OP_Transaction, i, eTxnType);
sqlite3VdbeUsesBtree(v, i);
}
}
@@ -115129,13 +117340,11 @@ SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){
** will occur at the end of the top-level VDBE and will be generated
** later, by sqlite3FinishCoding().
*/
-SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){
- Parse *pToplevel = sqlite3ParseToplevel(pParse);
-
- assert( iDb>=0 && iDb<pParse->db->nDb );
- assert( pParse->db->aDb[iDb].pBt!=0 || iDb==1 );
- assert( iDb<SQLITE_MAX_ATTACHED+2 );
- assert( sqlite3SchemaMutexHeld(pParse->db, iDb, 0) );
+static void sqlite3CodeVerifySchemaAtToplevel(Parse *pToplevel, int iDb){
+ assert( iDb>=0 && iDb<pToplevel->db->nDb );
+ assert( pToplevel->db->aDb[iDb].pBt!=0 || iDb==1 );
+ assert( iDb<SQLITE_MAX_DB );
+ assert( sqlite3SchemaMutexHeld(pToplevel->db, iDb, 0) );
if( DbMaskTest(pToplevel->cookieMask, iDb)==0 ){
DbMaskSet(pToplevel->cookieMask, iDb);
if( !OMIT_TEMPDB && iDb==1 ){
@@ -115143,6 +117352,10 @@ SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){
}
}
}
+SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){
+ sqlite3CodeVerifySchemaAtToplevel(sqlite3ParseToplevel(pParse), iDb);
+}
+
/*
** If argument zDb is NULL, then call sqlite3CodeVerifySchema() for each
@@ -115174,7 +117387,7 @@ SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb)
*/
SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){
Parse *pToplevel = sqlite3ParseToplevel(pParse);
- sqlite3CodeVerifySchema(pParse, iDb);
+ sqlite3CodeVerifySchemaAtToplevel(pToplevel, iDb);
DbMaskSet(pToplevel->writeMask, iDb);
pToplevel->isMultiWrite |= setStatement;
}
@@ -115225,7 +117438,9 @@ SQLITE_PRIVATE void sqlite3HaltConstraint(
i8 p4type, /* P4_STATIC or P4_TRANSIENT */
u8 p5Errmsg /* P5_ErrMsg type */
){
- Vdbe *v = sqlite3GetVdbe(pParse);
+ Vdbe *v;
+ assert( pParse->pVdbe!=0 );
+ v = sqlite3GetVdbe(pParse);
assert( (errCode&0xff)==SQLITE_CONSTRAINT || pParse->nested );
if( onError==OE_Abort ){
sqlite3MayAbort(pParse);
@@ -115471,23 +117686,75 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){
#ifndef SQLITE_OMIT_CTE
/*
+** Create a new CTE object
+*/
+SQLITE_PRIVATE Cte *sqlite3CteNew(
+ Parse *pParse, /* Parsing context */
+ Token *pName, /* Name of the common-table */
+ ExprList *pArglist, /* Optional column name list for the table */
+ Select *pQuery, /* Query used to initialize the table */
+ u8 eM10d /* The MATERIALIZED flag */
+){
+ Cte *pNew;
+ sqlite3 *db = pParse->db;
+
+ pNew = sqlite3DbMallocZero(db, sizeof(*pNew));
+ assert( pNew!=0 || db->mallocFailed );
+
+ if( db->mallocFailed ){
+ sqlite3ExprListDelete(db, pArglist);
+ sqlite3SelectDelete(db, pQuery);
+ }else{
+ pNew->pSelect = pQuery;
+ pNew->pCols = pArglist;
+ pNew->zName = sqlite3NameFromToken(pParse->db, pName);
+ pNew->eM10d = eM10d;
+ }
+ return pNew;
+}
+
+/*
+** Clear information from a Cte object, but do not deallocate storage
+** for the object itself.
+*/
+static void cteClear(sqlite3 *db, Cte *pCte){
+ assert( pCte!=0 );
+ sqlite3ExprListDelete(db, pCte->pCols);
+ sqlite3SelectDelete(db, pCte->pSelect);
+ sqlite3DbFree(db, pCte->zName);
+}
+
+/*
+** Free the contents of the CTE object passed as the second argument.
+*/
+SQLITE_PRIVATE void sqlite3CteDelete(sqlite3 *db, Cte *pCte){
+ assert( pCte!=0 );
+ cteClear(db, pCte);
+ sqlite3DbFree(db, pCte);
+}
+
+/*
** This routine is invoked once per CTE by the parser while parsing a
-** WITH clause.
+** WITH clause. The CTE described by teh third argument is added to
+** the WITH clause of the second argument. If the second argument is
+** NULL, then a new WITH argument is created.
*/
SQLITE_PRIVATE With *sqlite3WithAdd(
Parse *pParse, /* Parsing context */
With *pWith, /* Existing WITH clause, or NULL */
- Token *pName, /* Name of the common-table */
- ExprList *pArglist, /* Optional column name list for the table */
- Select *pQuery /* Query used to initialize the table */
+ Cte *pCte /* CTE to add to the WITH clause */
){
sqlite3 *db = pParse->db;
With *pNew;
char *zName;
+ if( pCte==0 ){
+ return pWith;
+ }
+
/* Check that the CTE name is unique within this WITH clause. If
** not, store an error in the Parse structure. */
- zName = sqlite3NameFromToken(pParse->db, pName);
+ zName = pCte->zName;
if( zName && pWith ){
int i;
for(i=0; i<pWith->nCte; i++){
@@ -115506,16 +117773,11 @@ SQLITE_PRIVATE With *sqlite3WithAdd(
assert( (pNew!=0 && zName!=0) || db->mallocFailed );
if( db->mallocFailed ){
- sqlite3ExprListDelete(db, pArglist);
- sqlite3SelectDelete(db, pQuery);
- sqlite3DbFree(db, zName);
+ sqlite3CteDelete(db, pCte);
pNew = pWith;
}else{
- pNew->a[pNew->nCte].pSelect = pQuery;
- pNew->a[pNew->nCte].pCols = pArglist;
- pNew->a[pNew->nCte].zName = zName;
- pNew->a[pNew->nCte].zCteErr = 0;
- pNew->nCte++;
+ pNew->a[pNew->nCte++] = *pCte;
+ sqlite3DbFree(db, pCte);
}
return pNew;
@@ -115528,10 +117790,7 @@ SQLITE_PRIVATE void sqlite3WithDelete(sqlite3 *db, With *pWith){
if( pWith ){
int i;
for(i=0; i<pWith->nCte; i++){
- struct Cte *pCte = &pWith->a[i];
- sqlite3ExprListDelete(db, pCte->pCols);
- sqlite3SelectDelete(db, pCte->pSelect);
- sqlite3DbFree(db, pCte->zName);
+ cteClear(db, &pWith->a[i]);
}
sqlite3DbFree(db, pWith);
}
@@ -116110,7 +118369,7 @@ SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){
**
*/
SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){
- struct SrcList_item *pItem = pSrc->a;
+ SrcItem *pItem = pSrc->a;
Table *pTab;
assert( pItem && pSrc->nSrc>=1 );
pTab = sqlite3LocateTableItem(pParse, 0, pItem);
@@ -116118,9 +118377,9 @@ SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){
pItem->pTab = pTab;
if( pTab ){
pTab->nTabRef++;
- }
- if( sqlite3IndexedByLookup(pParse, pItem) ){
- pTab = 0;
+ if( pItem->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pItem) ){
+ pTab = 0;
+ }
}
return pTab;
}
@@ -116288,9 +118547,15 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere(
/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
** and the SELECT subtree. */
pSrc->a[0].pTab = 0;
- pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0);
+ pSelectSrc = sqlite3SrcListDup(db, pSrc, 0);
pSrc->a[0].pTab = pTab;
- pSrc->a[0].pIBIndex = 0;
+ if( pSrc->a[0].fg.isIndexedBy ){
+ pSrc->a[0].u2.pIBIndex = 0;
+ pSrc->a[0].fg.isIndexedBy = 0;
+ sqlite3DbFree(db, pSrc->a[0].u1.zIndexedBy);
+ }else if( pSrc->a[0].fg.isCte ){
+ pSrc->a[0].u2.pCteUse->nUse++;
+ }
/* generate the SELECT expression tree. */
pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0,
@@ -116468,6 +118733,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
if( (db->flags & SQLITE_CountRows)!=0
&& !pParse->nested
&& !pParse->pTriggerTab
+ && !pParse->bReturning
){
memCnt = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
@@ -116502,11 +118768,14 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
assert( pIdx->pSchema==pTab->pSchema );
sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb);
+ if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){
+ sqlite3VdbeChangeP3(v, -1, memCnt ? memCnt : -1);
+ }
}
}else
#endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
{
- u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK|WHERE_SEEK_TABLE;
+ u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK;
if( sNC.ncFlags & NC_VarSelect ) bComplex = 1;
wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW);
if( HasRowid(pTab) ){
@@ -116542,6 +118811,9 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI );
assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF );
if( eOnePass!=ONEPASS_SINGLE ) sqlite3MultiWrite(pParse);
+ if( sqlite3WhereUsesDeferredSeek(pWInfo) ){
+ sqlite3VdbeAddOp1(v, OP_FinishSeek, iTabCur);
+ }
/* Keep track of the number of rows to be deleted */
if( memCnt ){
@@ -116576,6 +118848,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iTabCur] = 0;
if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iTabCur] = 0;
if( addrEphOpen ) sqlite3VdbeChangeToNoop(v, addrEphOpen);
+ addrBypass = sqlite3VdbeMakeLabel(pParse);
}else{
if( pPk ){
/* Add the PK key for this row to the temporary table */
@@ -116589,13 +118862,6 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
nKey = 1; /* OP_DeferredSeek always uses a single rowid */
sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey);
}
- }
-
- /* If this DELETE cannot use the ONEPASS strategy, this is the
- ** end of the WHERE loop */
- if( eOnePass!=ONEPASS_OFF ){
- addrBypass = sqlite3VdbeMakeLabel(pParse);
- }else{
sqlite3WhereEnd(pWInfo);
}
@@ -116692,7 +118958,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
** invoke the callback function.
*/
if( memCnt ){
- sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
+ sqlite3VdbeAddOp2(v, OP_ChngCntRow, memCnt, 1);
sqlite3VdbeSetNumCols(v, 1);
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC);
}
@@ -117016,20 +119282,18 @@ SQLITE_PRIVATE int sqlite3GenerateIndexKey(
continue;
}
sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j, regBase+j);
- /* If the column affinity is REAL but the number is an integer, then it
- ** might be stored in the table as an integer (using a compact
- ** representation) then converted to REAL by an OP_RealAffinity opcode.
- ** But we are getting ready to store this value back into an index, where
- ** it should be converted by to INTEGER again. So omit the OP_RealAffinity
- ** opcode if it is present */
- sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
+ if( pIdx->aiColumn[j]>=0 ){
+ /* If the column affinity is REAL but the number is an integer, then it
+ ** might be stored in the table as an integer (using a compact
+ ** representation) then converted to REAL by an OP_RealAffinity opcode.
+ ** But we are getting ready to store this value back into an index, where
+ ** it should be converted by to INTEGER again. So omit the
+ ** OP_RealAffinity opcode if it is present */
+ sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
+ }
}
if( regOut ){
sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut);
- if( pIdx->pTable->pSelect ){
- const char *zAff = sqlite3IndexAffinityStr(pParse->db, pIdx);
- sqlite3VdbeChangeP4(v, -1, zAff, P4_TRANSIENT);
- }
}
sqlite3ReleaseTempRange(pParse, regBase, nCol);
return regBase;
@@ -117744,7 +120008,8 @@ static int patternCompare(
/* Skip over multiple "*" characters in the pattern. If there
** are also "?" characters, skip those as well, but consume a
** single character of the input string for each "?" skipped */
- while( (c=Utf8Read(zPattern)) == matchAll || c == matchOne ){
+ while( (c=Utf8Read(zPattern)) == matchAll
+ || (c == matchOne && matchOne!=0) ){
if( c==matchOne && sqlite3Utf8Read(&zString)==0 ){
return SQLITE_NOWILDCARDMATCH;
}
@@ -118365,10 +120630,10 @@ static void trimFunc(
){
const unsigned char *zIn; /* Input string */
const unsigned char *zCharSet; /* Set of characters to trim */
- int nIn; /* Number of bytes in input */
+ unsigned int nIn; /* Number of bytes in input */
int flags; /* 1: trimleft 2: trimright 3: trim */
int i; /* Loop counter */
- unsigned char *aLen = 0; /* Length of each character in zCharSet */
+ unsigned int *aLen = 0; /* Length of each character in zCharSet */
unsigned char **azChar = 0; /* Individual characters in zCharSet */
int nChar; /* Number of characters in zCharSet */
@@ -118377,13 +120642,13 @@ static void trimFunc(
}
zIn = sqlite3_value_text(argv[0]);
if( zIn==0 ) return;
- nIn = sqlite3_value_bytes(argv[0]);
+ nIn = (unsigned)sqlite3_value_bytes(argv[0]);
assert( zIn==sqlite3_value_text(argv[0]) );
if( argc==1 ){
- static const unsigned char lenOne[] = { 1 };
+ static const unsigned lenOne[] = { 1 };
static unsigned char * const azOne[] = { (u8*)" " };
nChar = 1;
- aLen = (u8*)lenOne;
+ aLen = (unsigned*)lenOne;
azChar = (unsigned char **)azOne;
zCharSet = 0;
}else if( (zCharSet = sqlite3_value_text(argv[1]))==0 ){
@@ -118394,15 +120659,16 @@ static void trimFunc(
SQLITE_SKIP_UTF8(z);
}
if( nChar>0 ){
- azChar = contextMalloc(context, ((i64)nChar)*(sizeof(char*)+1));
+ azChar = contextMalloc(context,
+ ((i64)nChar)*(sizeof(char*)+sizeof(unsigned)));
if( azChar==0 ){
return;
}
- aLen = (unsigned char*)&azChar[nChar];
+ aLen = (unsigned*)&azChar[nChar];
for(z=zCharSet, nChar=0; *z; nChar++){
azChar[nChar] = (unsigned char *)z;
SQLITE_SKIP_UTF8(z);
- aLen[nChar] = (u8)(z - azChar[nChar]);
+ aLen[nChar] = (unsigned)(z - azChar[nChar]);
}
}
}
@@ -118410,7 +120676,7 @@ static void trimFunc(
flags = SQLITE_PTR_TO_INT(sqlite3_user_data(context));
if( flags & 1 ){
while( nIn>0 ){
- int len = 0;
+ unsigned int len = 0;
for(i=0; i<nChar; i++){
len = aLen[i];
if( len<=nIn && memcmp(zIn, azChar[i], len)==0 ) break;
@@ -118422,7 +120688,7 @@ static void trimFunc(
}
if( flags & 2 ){
while( nIn>0 ){
- int len = 0;
+ unsigned int len = 0;
for(i=0; i<nChar; i++){
len = aLen[i];
if( len<=nIn && memcmp(&zIn[nIn-len],azChar[i],len)==0 ) break;
@@ -118915,7 +121181,9 @@ SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive)
SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){
FuncDef *pDef;
int nExpr;
- if( pExpr->op!=TK_FUNCTION || !pExpr->x.pList ){
+ assert( pExpr!=0 );
+ assert( pExpr->op==TK_FUNCTION );
+ if( !pExpr->x.pList ){
return 0;
}
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
@@ -118954,6 +121222,201 @@ SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocas
return 1;
}
+/* Mathematical Constants */
+#ifndef M_PI
+# define M_PI 3.141592653589793238462643383279502884
+#endif
+#ifndef M_LN10
+# define M_LN10 2.302585092994045684017991454684364208
+#endif
+#ifndef M_LN2
+# define M_LN2 0.693147180559945309417232121458176568
+#endif
+
+
+/* Extra math functions that require linking with -lm
+*/
+#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
+/*
+** Implementation SQL functions:
+**
+** ceil(X)
+** ceiling(X)
+** floor(X)
+**
+** The sqlite3_user_data() pointer is a pointer to the libm implementation
+** of the underlying C function.
+*/
+static void ceilingFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ assert( argc==1 );
+ switch( sqlite3_value_numeric_type(argv[0]) ){
+ case SQLITE_INTEGER: {
+ sqlite3_result_int64(context, sqlite3_value_int64(argv[0]));
+ break;
+ }
+ case SQLITE_FLOAT: {
+ double (*x)(double) = (double(*)(double))sqlite3_user_data(context);
+ sqlite3_result_double(context, x(sqlite3_value_double(argv[0])));
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+}
+
+/*
+** On some systems, ceil() and floor() are intrinsic function. You are
+** unable to take a pointer to these functions. Hence, we here wrap them
+** in our own actual functions.
+*/
+static double xCeil(double x){ return ceil(x); }
+static double xFloor(double x){ return floor(x); }
+
+/*
+** Implementation of SQL functions:
+**
+** ln(X) - natural logarithm
+** log(X) - log X base 10
+** log10(X) - log X base 10
+** log(B,X) - log X base B
+*/
+static void logFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ double x, b, ans;
+ assert( argc==1 || argc==2 );
+ switch( sqlite3_value_numeric_type(argv[0]) ){
+ case SQLITE_INTEGER:
+ case SQLITE_FLOAT:
+ x = sqlite3_value_double(argv[0]);
+ if( x<=0.0 ) return;
+ break;
+ default:
+ return;
+ }
+ if( argc==2 ){
+ switch( sqlite3_value_numeric_type(argv[0]) ){
+ case SQLITE_INTEGER:
+ case SQLITE_FLOAT:
+ b = log(x);
+ if( b<=0.0 ) return;
+ x = sqlite3_value_double(argv[1]);
+ if( x<=0.0 ) return;
+ break;
+ default:
+ return;
+ }
+ ans = log(x)/b;
+ }else{
+ ans = log(x);
+ switch( SQLITE_PTR_TO_INT(sqlite3_user_data(context)) ){
+ case 1:
+ /* Convert from natural logarithm to log base 10 */
+ ans *= 1.0/M_LN10;
+ break;
+ case 2:
+ /* Convert from natural logarithm to log base 2 */
+ ans *= 1.0/M_LN2;
+ break;
+ default:
+ break;
+ }
+ }
+ sqlite3_result_double(context, ans);
+}
+
+/*
+** Functions to converts degrees to radians and radians to degrees.
+*/
+static double degToRad(double x){ return x*(M_PI/180.0); }
+static double radToDeg(double x){ return x*(180.0/M_PI); }
+
+/*
+** Implementation of 1-argument SQL math functions:
+**
+** exp(X) - Compute e to the X-th power
+*/
+static void math1Func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int type0;
+ double v0, ans;
+ double (*x)(double);
+ assert( argc==1 );
+ type0 = sqlite3_value_numeric_type(argv[0]);
+ if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return;
+ v0 = sqlite3_value_double(argv[0]);
+ x = (double(*)(double))sqlite3_user_data(context);
+ ans = x(v0);
+ sqlite3_result_double(context, ans);
+}
+
+/*
+** Implementation of 2-argument SQL math functions:
+**
+** power(X,Y) - Compute X to the Y-th power
+*/
+static void math2Func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int type0, type1;
+ double v0, v1, ans;
+ double (*x)(double,double);
+ assert( argc==2 );
+ type0 = sqlite3_value_numeric_type(argv[0]);
+ if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return;
+ type1 = sqlite3_value_numeric_type(argv[1]);
+ if( type1!=SQLITE_INTEGER && type1!=SQLITE_FLOAT ) return;
+ v0 = sqlite3_value_double(argv[0]);
+ v1 = sqlite3_value_double(argv[1]);
+ x = (double(*)(double,double))sqlite3_user_data(context);
+ ans = x(v0, v1);
+ sqlite3_result_double(context, ans);
+}
+
+/*
+** Implementation of 0-argument pi() function.
+*/
+static void piFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ assert( argc==0 );
+ sqlite3_result_double(context, M_PI);
+}
+
+#endif /* SQLITE_ENABLE_MATH_FUNCTIONS */
+
+/*
+** Implementation of sign(X) function.
+*/
+static void signFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int type0;
+ double x;
+ UNUSED_PARAMETER(argc);
+ assert( argc==1 );
+ type0 = sqlite3_value_numeric_type(argv[0]);
+ if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return;
+ x = sqlite3_value_double(argv[0]);
+ sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0);
+}
+
/*
** All of the FuncDef structures in the aBuiltinFunc[] array above
** to the global function hash table. This occurs at start-time (as
@@ -119045,6 +121508,8 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc ),
FUNCTION(substr, 2, 0, 0, substrFunc ),
FUNCTION(substr, 3, 0, 0, substrFunc ),
+ FUNCTION(substring, 2, 0, 0, substrFunc ),
+ FUNCTION(substring, 3, 0, 0, substrFunc ),
WAGGREGATE(sum, 1,0,0, sumStep, sumFinalize, sumFinalize, sumInverse, 0),
WAGGREGATE(total, 1,0,0, sumStep,totalFinalize,totalFinalize,sumInverse, 0),
WAGGREGATE(avg, 1,0,0, sumStep, avgFinalize, avgFinalize, sumInverse, 0),
@@ -119070,6 +121535,43 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
#endif
FUNCTION(coalesce, 1, 0, 0, 0 ),
FUNCTION(coalesce, 0, 0, 0, 0 ),
+#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
+ MFUNCTION(ceil, 1, xCeil, ceilingFunc ),
+ MFUNCTION(ceiling, 1, xCeil, ceilingFunc ),
+ MFUNCTION(floor, 1, xFloor, ceilingFunc ),
+#if SQLITE_HAVE_C99_MATH_FUNCS
+ MFUNCTION(trunc, 1, trunc, ceilingFunc ),
+#endif
+ FUNCTION(ln, 1, 0, 0, logFunc ),
+ FUNCTION(log, 1, 1, 0, logFunc ),
+ FUNCTION(log10, 1, 1, 0, logFunc ),
+ FUNCTION(log2, 1, 2, 0, logFunc ),
+ FUNCTION(log, 2, 0, 0, logFunc ),
+ MFUNCTION(exp, 1, exp, math1Func ),
+ MFUNCTION(pow, 2, pow, math2Func ),
+ MFUNCTION(power, 2, pow, math2Func ),
+ MFUNCTION(mod, 2, fmod, math2Func ),
+ MFUNCTION(acos, 1, acos, math1Func ),
+ MFUNCTION(asin, 1, asin, math1Func ),
+ MFUNCTION(atan, 1, atan, math1Func ),
+ MFUNCTION(atan2, 2, atan2, math2Func ),
+ MFUNCTION(cos, 1, cos, math1Func ),
+ MFUNCTION(sin, 1, sin, math1Func ),
+ MFUNCTION(tan, 1, tan, math1Func ),
+ MFUNCTION(cosh, 1, cosh, math1Func ),
+ MFUNCTION(sinh, 1, sinh, math1Func ),
+ MFUNCTION(tanh, 1, tanh, math1Func ),
+#if SQLITE_HAVE_C99_MATH_FUNCS
+ MFUNCTION(acosh, 1, acosh, math1Func ),
+ MFUNCTION(asinh, 1, asinh, math1Func ),
+ MFUNCTION(atanh, 1, atanh, math1Func ),
+#endif
+ MFUNCTION(sqrt, 1, sqrt, math1Func ),
+ MFUNCTION(radians, 1, degToRad, math1Func ),
+ MFUNCTION(degrees, 1, radToDeg, math1Func ),
+ FUNCTION(pi, 0, 0, 0, piFunc ),
+#endif /* SQLITE_ENABLE_MATH_FUNCTIONS */
+ FUNCTION(sign, 1, 0, 0, signFunc ),
INLINE_FUNC(coalesce, -1, INLINEFUNC_coalesce, 0 ),
INLINE_FUNC(iif, 3, INLINEFUNC_iif, 0 ),
};
@@ -120125,7 +122627,7 @@ SQLITE_PRIVATE void sqlite3FkCheck(
** child table as a SrcList for sqlite3WhereBegin() */
pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0);
if( pSrc ){
- struct SrcList_item *pItem = pSrc->a;
+ SrcItem *pItem = pSrc->a;
pItem->pTab = pFKey->pFrom;
pItem->zName = pFKey->pFrom->zName;
pItem->pTab->nTabRef++;
@@ -120213,7 +122715,9 @@ SQLITE_PRIVATE u32 sqlite3FkOldmask(
**
** For an UPDATE, this function returns 2 if:
**
-** * There are any FKs for which pTab is the child and the parent table, or
+** * There are any FKs for which pTab is the child and the parent table
+** and any FK processing at all is required (even of a different FK), or
+**
** * the UPDATE modifies one or more parent keys for which the action is
** not "NO ACTION" (i.e. is CASCADE, SET DEFAULT or SET NULL).
**
@@ -120225,13 +122729,14 @@ SQLITE_PRIVATE int sqlite3FkRequired(
int *aChange, /* Non-NULL for UPDATE operations */
int chngRowid /* True for UPDATE that affects rowid */
){
- int eRet = 0;
+ int eRet = 1; /* Value to return if bHaveFK is true */
+ int bHaveFK = 0; /* If FK processing is required */
if( pParse->db->flags&SQLITE_ForeignKeys ){
if( !aChange ){
/* A DELETE operation. Foreign key processing is required if the
** table in question is either the child or parent table for any
** foreign key constraint. */
- eRet = (sqlite3FkReferences(pTab) || pTab->pFKey);
+ bHaveFK = (sqlite3FkReferences(pTab) || pTab->pFKey);
}else{
/* This is an UPDATE. Foreign key processing is only required if the
** operation modifies one or more child or parent key columns. */
@@ -120239,9 +122744,9 @@ SQLITE_PRIVATE int sqlite3FkRequired(
/* Check if any child key columns are being modified. */
for(p=pTab->pFKey; p; p=p->pNextFrom){
- if( 0==sqlite3_stricmp(pTab->zName, p->zTo) ) return 2;
if( fkChildIsModified(pTab, p, aChange, chngRowid) ){
- eRet = 1;
+ if( 0==sqlite3_stricmp(pTab->zName, p->zTo) ) eRet = 2;
+ bHaveFK = 1;
}
}
@@ -120249,12 +122754,12 @@ SQLITE_PRIVATE int sqlite3FkRequired(
for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){
if( fkParentIsModified(pTab, p, aChange, chngRowid) ){
if( p->aAction[1]!=OE_None ) return 2;
- eRet = 1;
+ bHaveFK = 1;
}
}
}
}
- return eRet;
+ return bHaveFK ? eRet : 0;
}
/*
@@ -120585,7 +123090,8 @@ SQLITE_PRIVATE void sqlite3OpenTable(
){
Vdbe *v;
assert( !IsVirtual(pTab) );
- v = sqlite3GetVdbe(pParse);
+ assert( pParse->pVdbe!=0 );
+ v = pParse->pVdbe;
assert( opcode==OP_OpenWrite || opcode==OP_OpenRead );
sqlite3TableLock(pParse, iDb, pTab->tnum,
(opcode==OP_OpenWrite)?1:0, pTab->zName);
@@ -120910,7 +123416,7 @@ static int autoIncBegin(
** Ticket d8dc2b3a58cd5dc2918a1d4acb 2018-05-23 */
if( pSeqTab==0
|| !HasRowid(pSeqTab)
- || IsVirtual(pSeqTab)
+ || NEVER(IsVirtual(pSeqTab))
|| pSeqTab->nCol!=2
){
pParse->nErr++;
@@ -120922,7 +123428,9 @@ static int autoIncBegin(
while( pInfo && pInfo->pTab!=pTab ){ pInfo = pInfo->pNext; }
if( pInfo==0 ){
pInfo = sqlite3DbMallocRawNN(pParse->db, sizeof(*pInfo));
- if( pInfo==0 ) return 0;
+ sqlite3ParserAddCleanup(pToplevel, sqlite3DbFree, pInfo);
+ testcase( pParse->earlyCleanup );
+ if( pParse->db->mallocFailed ) return 0;
pInfo->pNext = pToplevel->pAinc;
pToplevel->pAinc = pInfo;
pInfo->pTab = pTab;
@@ -121367,7 +123875,7 @@ SQLITE_PRIVATE void sqlite3Insert(
bIdListInOrder = 0;
}else{
sqlite3ErrorMsg(pParse, "table %S has no column named %s",
- pTabList, 0, pColumn->a[i].zName);
+ pTabList->a, pColumn->a[i].zName);
pParse->checkSchema = 1;
goto insert_cleanup;
}
@@ -121480,19 +123988,24 @@ SQLITE_PRIVATE void sqlite3Insert(
}
}
#endif
- }
- /* Make sure the number of columns in the source data matches the number
- ** of columns to be inserted into the table.
- */
- for(i=0; i<pTab->nCol; i++){
- if( pTab->aCol[i].colFlags & COLFLAG_NOINSERT ) nHidden++;
- }
- if( pColumn==0 && nColumn && nColumn!=(pTab->nCol-nHidden) ){
- sqlite3ErrorMsg(pParse,
- "table %S has %d columns but %d values were supplied",
- pTabList, 0, pTab->nCol-nHidden, nColumn);
- goto insert_cleanup;
+ /* Make sure the number of columns in the source data matches the number
+ ** of columns to be inserted into the table.
+ */
+ assert( TF_HasHidden==COLFLAG_HIDDEN );
+ assert( TF_HasGenerated==COLFLAG_GENERATED );
+ assert( COLFLAG_NOINSERT==(COLFLAG_GENERATED|COLFLAG_HIDDEN) );
+ if( (pTab->tabFlags & (TF_HasGenerated|TF_HasHidden))!=0 ){
+ for(i=0; i<pTab->nCol; i++){
+ if( pTab->aCol[i].colFlags & COLFLAG_NOINSERT ) nHidden++;
+ }
+ }
+ if( nColumn!=(pTab->nCol-nHidden) ){
+ sqlite3ErrorMsg(pParse,
+ "table %S has %d columns but %d values were supplied",
+ pTabList->a, pTab->nCol-nHidden, nColumn);
+ goto insert_cleanup;
+ }
}
if( pColumn!=0 && nColumn!=pColumn->nId ){
sqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId);
@@ -121504,6 +124017,7 @@ SQLITE_PRIVATE void sqlite3Insert(
if( (db->flags & SQLITE_CountRows)!=0
&& !pParse->nested
&& !pParse->pTriggerTab
+ && !pParse->bReturning
){
regRowCount = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
@@ -121527,6 +124041,7 @@ SQLITE_PRIVATE void sqlite3Insert(
}
#ifndef SQLITE_OMIT_UPSERT
if( pUpsert ){
+ Upsert *pNx;
if( IsVirtual(pTab) ){
sqlite3ErrorMsg(pParse, "UPSERT not implemented for virtual table \"%s\"",
pTab->zName);
@@ -121540,13 +124055,19 @@ SQLITE_PRIVATE void sqlite3Insert(
goto insert_cleanup;
}
pTabList->a[0].iCursor = iDataCur;
- pUpsert->pUpsertSrc = pTabList;
- pUpsert->regData = regData;
- pUpsert->iDataCur = iDataCur;
- pUpsert->iIdxCur = iIdxCur;
- if( pUpsert->pUpsertTarget ){
- sqlite3UpsertAnalyzeTarget(pParse, pTabList, pUpsert);
- }
+ pNx = pUpsert;
+ do{
+ pNx->pUpsertSrc = pTabList;
+ pNx->regData = regData;
+ pNx->iDataCur = iDataCur;
+ pNx->iIdxCur = iIdxCur;
+ if( pNx->pUpsertTarget ){
+ if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx) ){
+ goto insert_cleanup;
+ }
+ }
+ pNx = pNx->pNextUpsert;
+ }while( pNx!=0 );
}
#endif
@@ -121687,11 +124208,6 @@ SQLITE_PRIVATE void sqlite3Insert(
sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); VdbeCoverage(v);
}
- /* Cannot have triggers on a virtual table. If it were possible,
- ** this block would have to account for hidden column.
- */
- assert( !IsVirtual(pTab) );
-
/* Copy the new data already generated. */
assert( pTab->nNVCol>0 );
sqlite3VdbeAddOp3(v, OP_Copy, regRowid+1, regCols+1, pTab->nNVCol-1);
@@ -121790,7 +124306,7 @@ SQLITE_PRIVATE void sqlite3Insert(
}else
#endif
{
- int isReplace; /* Set to true if constraints may cause a replace */
+ int isReplace = 0;/* Set to true if constraints may cause a replace */
int bUseSeek; /* True to use OPFLAG_SEEKRESULT */
sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert
@@ -121810,6 +124326,13 @@ SQLITE_PRIVATE void sqlite3Insert(
regIns, aRegIdx, 0, appendFlag, bUseSeek
);
}
+#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
+ }else if( pParse->bReturning ){
+ /* If there is a RETURNING clause, populate the rowid register with
+ ** constant value -1, in case one or more of the returned expressions
+ ** refer to the "rowid" of the view. */
+ sqlite3VdbeAddOp2(v, OP_Integer, -1, regRowid);
+#endif
}
/* Update the count of rows that are inserted
@@ -121846,7 +124369,9 @@ SQLITE_PRIVATE void sqlite3Insert(
sqlite3VdbeJumpHere(v, addrInsTop);
}
+#ifndef SQLITE_OMIT_XFER_OPT
insert_end:
+#endif /* SQLITE_OMIT_XFER_OPT */
/* Update the sqlite_sequence table by storing the content of the
** maximum rowid counter values recorded while inserting into
** autoincrement tables.
@@ -121861,7 +124386,7 @@ insert_end:
** invoke the callback function.
*/
if( regRowCount ){
- sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
+ sqlite3VdbeAddOp2(v, OP_ChngCntRow, regRowCount, 1);
sqlite3VdbeSetNumCols(v, 1);
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", SQLITE_STATIC);
}
@@ -121952,6 +124477,70 @@ SQLITE_PRIVATE int sqlite3ExprReferencesUpdatedColumn(
}
/*
+** The sqlite3GenerateConstraintChecks() routine usually wants to visit
+** the indexes of a table in the order provided in the Table->pIndex list.
+** However, sometimes (rarely - when there is an upsert) it wants to visit
+** the indexes in a different order. The following data structures accomplish
+** this.
+**
+** The IndexIterator object is used to walk through all of the indexes
+** of a table in either Index.pNext order, or in some other order established
+** by an array of IndexListTerm objects.
+*/
+typedef struct IndexListTerm IndexListTerm;
+typedef struct IndexIterator IndexIterator;
+struct IndexIterator {
+ int eType; /* 0 for Index.pNext list. 1 for an array of IndexListTerm */
+ int i; /* Index of the current item from the list */
+ union {
+ struct { /* Use this object for eType==0: A Index.pNext list */
+ Index *pIdx; /* The current Index */
+ } lx;
+ struct { /* Use this object for eType==1; Array of IndexListTerm */
+ int nIdx; /* Size of the array */
+ IndexListTerm *aIdx; /* Array of IndexListTerms */
+ } ax;
+ } u;
+};
+
+/* When IndexIterator.eType==1, then each index is an array of instances
+** of the following object
+*/
+struct IndexListTerm {
+ Index *p; /* The index */
+ int ix; /* Which entry in the original Table.pIndex list is this index*/
+};
+
+/* Return the first index on the list */
+static Index *indexIteratorFirst(IndexIterator *pIter, int *pIx){
+ assert( pIter->i==0 );
+ if( pIter->eType ){
+ *pIx = pIter->u.ax.aIdx[0].ix;
+ return pIter->u.ax.aIdx[0].p;
+ }else{
+ *pIx = 0;
+ return pIter->u.lx.pIdx;
+ }
+}
+
+/* Return the next index from the list. Return NULL when out of indexes */
+static Index *indexIteratorNext(IndexIterator *pIter, int *pIx){
+ if( pIter->eType ){
+ int i = ++pIter->i;
+ if( i>=pIter->u.ax.nIdx ){
+ *pIx = i;
+ return 0;
+ }
+ *pIx = pIter->u.ax.aIdx[i].ix;
+ return pIter->u.ax.aIdx[i].p;
+ }else{
+ ++(*pIx);
+ pIter->u.lx.pIdx = pIter->u.lx.pIdx->pNext;
+ return pIter->u.lx.pIdx;
+ }
+}
+
+/*
** Generate code to do constraint checks prior to an INSERT or an UPDATE
** on table pTab.
**
@@ -122059,7 +124648,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
){
Vdbe *v; /* VDBE under constrution */
Index *pIdx; /* Pointer to one of the indices */
- Index *pPk = 0; /* The PRIMARY KEY index */
+ Index *pPk = 0; /* The PRIMARY KEY index for WITHOUT ROWID tables */
sqlite3 *db; /* Database connection */
int i; /* loop counter */
int ix; /* Index loop counter */
@@ -122067,11 +124656,11 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
int onError; /* Conflict resolution strategy */
int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */
int nPkField; /* Number of fields in PRIMARY KEY. 1 for ROWID tables */
- Index *pUpIdx = 0; /* Index to which to apply the upsert */
- u8 isUpdate; /* True if this is an UPDATE operation */
+ Upsert *pUpsertClause = 0; /* The specific ON CONFLICT clause for pIdx */
+ u8 isUpdate; /* True if this is an UPDATE operation */
u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */
- int upsertBypass = 0; /* Address of Goto to bypass upsert subroutine */
- int upsertJump = 0; /* Address of Goto that jumps into upsert subroutine */
+ int upsertIpkReturn = 0; /* Address of Goto at end of IPK uniqueness check */
+ int upsertIpkDelay = 0; /* Address of Goto to bypass initial IPK check */
int ipkTop = 0; /* Top of the IPK uniqueness check */
int ipkBottom = 0; /* OP_Goto at the end of the IPK uniqueness check */
/* Variables associated with retesting uniqueness constraints after
@@ -122081,10 +124670,11 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
int lblRecheckOk = 0; /* Each recheck jumps to this label if it passes */
Trigger *pTrigger; /* List of DELETE triggers on the table pTab */
int nReplaceTrig = 0; /* Number of replace triggers coded */
+ IndexIterator sIdxIter; /* Index iterator */
isUpdate = regOldData!=0;
db = pParse->db;
- v = sqlite3GetVdbe(pParse);
+ v = pParse->pVdbe;
assert( v!=0 );
assert( pTab->pSelect==0 ); /* This table is not a VIEW */
nCol = pTab->nCol;
@@ -122238,7 +124828,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
sqlite3VdbeGoto(v, ignoreDest);
}else{
char *zName = pCheck->a[i].zEName;
- if( zName==0 ) zName = pTab->zName;
+ assert( zName!=0 || pParse->db->mallocFailed );
if( onError==OE_Replace ) onError = OE_Abort; /* IMP: R-26383-51744 */
sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_CHECK,
onError, zName, P4_TRANSIENT,
@@ -122278,19 +124868,63 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
** list of indexes attached to a table puts all OE_Replace indexes last
** in the list. See sqlite3CreateIndex() for where that happens.
*/
-
+ sIdxIter.eType = 0;
+ sIdxIter.i = 0;
+ sIdxIter.u.ax.aIdx = 0; /* Silence harmless compiler warning */
+ sIdxIter.u.lx.pIdx = pTab->pIndex;
if( pUpsert ){
if( pUpsert->pUpsertTarget==0 ){
- /* An ON CONFLICT DO NOTHING clause, without a constraint-target.
- ** Make all unique constraint resolution be OE_Ignore */
- assert( pUpsert->pUpsertSet==0 );
- overrideError = OE_Ignore;
- pUpsert = 0;
- }else if( (pUpIdx = pUpsert->pUpsertIdx)!=0 ){
- /* If the constraint-target uniqueness check must be run first.
- ** Jump to that uniqueness check now */
- upsertJump = sqlite3VdbeAddOp0(v, OP_Goto);
- VdbeComment((v, "UPSERT constraint goes first"));
+ /* There is just on ON CONFLICT clause and it has no constraint-target */
+ assert( pUpsert->pNextUpsert==0 );
+ if( pUpsert->isDoUpdate==0 ){
+ /* A single ON CONFLICT DO NOTHING clause, without a constraint-target.
+ ** Make all unique constraint resolution be OE_Ignore */
+ overrideError = OE_Ignore;
+ pUpsert = 0;
+ }else{
+ /* A single ON CONFLICT DO UPDATE. Make all resolutions OE_Update */
+ overrideError = OE_Update;
+ }
+ }else if( pTab->pIndex!=0 ){
+ /* Otherwise, we'll need to run the IndexListTerm array version of the
+ ** iterator to ensure that all of the ON CONFLICT conditions are
+ ** checked first and in order. */
+ int nIdx, jj;
+ u64 nByte;
+ Upsert *pTerm;
+ u8 *bUsed;
+ for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){
+ assert( aRegIdx[nIdx]>0 );
+ }
+ sIdxIter.eType = 1;
+ sIdxIter.u.ax.nIdx = nIdx;
+ nByte = (sizeof(IndexListTerm)+1)*nIdx + nIdx;
+ sIdxIter.u.ax.aIdx = sqlite3DbMallocZero(db, nByte);
+ if( sIdxIter.u.ax.aIdx==0 ) return; /* OOM */
+ bUsed = (u8*)&sIdxIter.u.ax.aIdx[nIdx];
+ pUpsert->pToFree = sIdxIter.u.ax.aIdx;
+ for(i=0, pTerm=pUpsert; pTerm; pTerm=pTerm->pNextUpsert){
+ if( pTerm->pUpsertTarget==0 ) break;
+ if( pTerm->pUpsertIdx==0 ) continue; /* Skip ON CONFLICT for the IPK */
+ jj = 0;
+ pIdx = pTab->pIndex;
+ while( ALWAYS(pIdx!=0) && pIdx!=pTerm->pUpsertIdx ){
+ pIdx = pIdx->pNext;
+ jj++;
+ }
+ if( bUsed[jj] ) continue; /* Duplicate ON CONFLICT clause ignored */
+ bUsed[jj] = 1;
+ sIdxIter.u.ax.aIdx[i].p = pIdx;
+ sIdxIter.u.ax.aIdx[i].ix = jj;
+ i++;
+ }
+ for(jj=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, jj++){
+ if( bUsed[jj] ) continue;
+ sIdxIter.u.ax.aIdx[i].p = pIdx;
+ sIdxIter.u.ax.aIdx[i].ix = jj;
+ i++;
+ }
+ assert( i==nIdx );
}
}
@@ -122353,11 +124987,20 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
}
/* figure out whether or not upsert applies in this case */
- if( pUpsert && pUpsert->pUpsertIdx==0 ){
- if( pUpsert->pUpsertSet==0 ){
- onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */
- }else{
- onError = OE_Update; /* DO UPDATE */
+ if( pUpsert ){
+ pUpsertClause = sqlite3UpsertOfIndex(pUpsert,0);
+ if( pUpsertClause!=0 ){
+ if( pUpsertClause->isDoUpdate==0 ){
+ onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */
+ }else{
+ onError = OE_Update; /* DO UPDATE */
+ }
+ }
+ if( pUpsertClause!=pUpsert ){
+ /* The first ON CONFLICT clause has a conflict target other than
+ ** the IPK. We have to jump ahead to that first ON CONFLICT clause
+ ** and then come back here and deal with the IPK afterwards */
+ upsertIpkDelay = sqlite3VdbeAddOp0(v, OP_Goto);
}
}
@@ -122367,7 +125010,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
** the UNIQUE constraints have run.
*/
if( onError==OE_Replace /* IPK rule is REPLACE */
- && onError!=overrideError /* Rules for other contraints are different */
+ && onError!=overrideError /* Rules for other constraints are different */
&& pTab->pIndex /* There exist other constraints */
){
ipkTop = sqlite3VdbeAddOp0(v, OP_Goto)+1;
@@ -122464,7 +125107,9 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
}
}
sqlite3VdbeResolveLabel(v, addrRowidOk);
- if( ipkTop ){
+ if( pUpsert && pUpsertClause!=pUpsert ){
+ upsertIpkReturn = sqlite3VdbeAddOp0(v, OP_Goto);
+ }else if( ipkTop ){
ipkBottom = sqlite3VdbeAddOp0(v, OP_Goto);
sqlite3VdbeJumpHere(v, ipkTop-1);
}
@@ -122477,7 +125122,10 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
** This loop also handles the case of the PRIMARY KEY index for a
** WITHOUT ROWID table.
*/
- for(ix=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, ix++){
+ for(pIdx = indexIteratorFirst(&sIdxIter, &ix);
+ pIdx;
+ pIdx = indexIteratorNext(&sIdxIter, &ix)
+ ){
int regIdx; /* Range of registers hold conent for pIdx */
int regR; /* Range of registers holding conflicting PK */
int iThisCur; /* Cursor for this UNIQUE index */
@@ -122485,15 +125133,14 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
int addrConflictCk; /* First opcode in the conflict check logic */
if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */
- if( pUpIdx==pIdx ){
- addrUniqueOk = upsertJump+1;
- upsertBypass = sqlite3VdbeGoto(v, 0);
- VdbeComment((v, "Skip upsert subroutine"));
- sqlite3VdbeJumpHere(v, upsertJump);
- }else{
- addrUniqueOk = sqlite3VdbeMakeLabel(pParse);
+ if( pUpsert ){
+ pUpsertClause = sqlite3UpsertOfIndex(pUpsert, pIdx);
+ if( upsertIpkDelay && pUpsertClause==pUpsert ){
+ sqlite3VdbeJumpHere(v, upsertIpkDelay);
+ }
}
- if( bAffinityDone==0 && (pUpIdx==0 || pUpIdx==pIdx) ){
+ addrUniqueOk = sqlite3VdbeMakeLabel(pParse);
+ if( bAffinityDone==0 ){
sqlite3TableAffinity(v, pTab, regNewData+1);
bAffinityDone = 1;
}
@@ -122564,8 +125211,8 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
}
/* Figure out if the upsert clause applies to this index */
- if( pUpIdx==pIdx ){
- if( pUpsert->pUpsertSet==0 ){
+ if( pUpsertClause ){
+ if( pUpsertClause->isDoUpdate==0 ){
onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */
}else{
onError = OE_Update; /* DO UPDATE */
@@ -122603,7 +125250,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
regIdx, pIdx->nKeyCol); VdbeCoverage(v);
/* Generate code to handle collisions */
- regR = (pIdx==pPk) ? regIdx : sqlite3GetTempRange(pParse, nPkField);
+ regR = pIdx==pPk ? regIdx : sqlite3GetTempRange(pParse, nPkField);
if( isUpdate || onError==OE_Replace ){
if( HasRowid(pTab) ){
sqlite3VdbeAddOp2(v, OP_IdxRowid, iThisCur, regR);
@@ -122755,13 +125402,16 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
break;
}
}
- if( pUpIdx==pIdx ){
- sqlite3VdbeGoto(v, upsertJump+1);
- sqlite3VdbeJumpHere(v, upsertBypass);
- }else{
- sqlite3VdbeResolveLabel(v, addrUniqueOk);
- }
+ sqlite3VdbeResolveLabel(v, addrUniqueOk);
if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField);
+ if( pUpsertClause
+ && upsertIpkReturn
+ && sqlite3UpsertNextIsIPK(pUpsertClause)
+ ){
+ sqlite3VdbeGoto(v, upsertIpkDelay+1);
+ sqlite3VdbeJumpHere(v, upsertIpkReturn);
+ upsertIpkReturn = 0;
+ }
}
/* If the IPK constraint is a REPLACE, run it last */
@@ -122828,6 +125478,32 @@ SQLITE_PRIVATE void sqlite3SetMakeRecordP5(Vdbe *v, Table *pTab){
#endif
/*
+** Table pTab is a WITHOUT ROWID table that is being written to. The cursor
+** number is iCur, and register regData contains the new record for the
+** PK index. This function adds code to invoke the pre-update hook,
+** if one is registered.
+*/
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+static void codeWithoutRowidPreupdate(
+ Parse *pParse, /* Parse context */
+ Table *pTab, /* Table being updated */
+ int iCur, /* Cursor number for table */
+ int regData /* Data containing new record */
+){
+ Vdbe *v = pParse->pVdbe;
+ int r = sqlite3GetTempReg(pParse);
+ assert( !HasRowid(pTab) );
+ assert( 0==(pParse->db->mDbFlags & DBFLAG_Vacuum) || CORRUPT_DB );
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, r);
+ sqlite3VdbeAddOp4(v, OP_Insert, iCur, regData, r, (char*)pTab, P4_TABLE);
+ sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP);
+ sqlite3ReleaseTempReg(pParse, r);
+}
+#else
+# define codeWithoutRowidPreupdate(a,b,c,d)
+#endif
+
+/*
** This routine generates code to finish the INSERT or UPDATE operation
** that was started by a prior call to sqlite3GenerateConstraintChecks.
** A consecutive range of registers starting at regNewData contains the
@@ -122857,7 +125533,7 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion(
|| update_flags==(OPFLAG_ISUPDATE|OPFLAG_SAVEPOSITION)
);
- v = sqlite3GetVdbe(pParse);
+ v = pParse->pVdbe;
assert( v!=0 );
assert( pTab->pSelect==0 ); /* This table is not a VIEW */
for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
@@ -122875,17 +125551,9 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion(
assert( pParse->nested==0 );
pik_flags |= OPFLAG_NCHANGE;
pik_flags |= (update_flags & OPFLAG_SAVEPOSITION);
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
if( update_flags==0 ){
- int r = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp2(v, OP_Integer, 0, r);
- sqlite3VdbeAddOp4(v, OP_Insert,
- iIdxCur+i, aRegIdx[i], r, (char*)pTab, P4_TABLE
- );
- sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP);
- sqlite3ReleaseTempReg(pParse, r);
+ codeWithoutRowidPreupdate(pParse, pTab, iIdxCur+i, aRegIdx[i]);
}
-#endif
}
sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i],
aRegIdx[i]+1,
@@ -122958,7 +125626,7 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices(
return 0;
}
iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
- v = sqlite3GetVdbe(pParse);
+ v = pParse->pVdbe;
assert( v!=0 );
if( iBase<0 ) iBase = pParse->nTab;
iDataCur = iBase++;
@@ -123083,7 +125751,7 @@ static int xferOptimization(
ExprList *pEList; /* The result set of the SELECT */
Table *pSrc; /* The table in the FROM clause of SELECT */
Index *pSrcIdx, *pDestIdx; /* Source and destination indices */
- struct SrcList_item *pItem; /* An element of pSelect->pSrc */
+ SrcItem *pItem; /* An element of pSelect->pSrc */
int i; /* Loop counter */
int iDbSrc; /* The database of pSrc */
int iSrc, iDest; /* Cursors from source and destination */
@@ -123300,6 +125968,7 @@ static int xferOptimization(
iDest = pParse->nTab++;
regAutoinc = autoIncBegin(pParse, iDbDest, pDest);
regData = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp2(v, OP_Null, 0, regData);
regRowid = sqlite3GetTempReg(pParse);
sqlite3OpenTable(pParse, iDest, iDbDest, pDest, OP_OpenWrite);
assert( HasRowid(pDest) || destHasUniqueIdx );
@@ -123335,11 +126004,13 @@ static int xferOptimization(
emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v);
if( pDest->iPKey>=0 ){
addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid);
- sqlite3VdbeVerifyAbortable(v, onError);
- addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid);
- VdbeCoverage(v);
- sqlite3RowidConstraint(pParse, onError, pDest);
- sqlite3VdbeJumpHere(v, addr2);
+ if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){
+ sqlite3VdbeVerifyAbortable(v, onError);
+ addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid);
+ VdbeCoverage(v);
+ sqlite3RowidConstraint(pParse, onError, pDest);
+ sqlite3VdbeJumpHere(v, addr2);
+ }
autoIncStep(pParse, regAutoinc, regRowid);
}else if( pDest->pIndex==0 && !(db->mDbFlags & DBFLAG_VacuumInto) ){
addr1 = sqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid);
@@ -123347,16 +126018,28 @@ static int xferOptimization(
addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid);
assert( (pDest->tabFlags & TF_Autoincrement)==0 );
}
+
if( db->mDbFlags & DBFLAG_Vacuum ){
sqlite3VdbeAddOp1(v, OP_SeekEnd, iDest);
- insFlags = OPFLAG_APPEND|OPFLAG_USESEEKRESULT;
+ insFlags = OPFLAG_APPEND|OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT;
}else{
- insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND;
+ insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND|OPFLAG_PREFORMAT;
+ }
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){
+ sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1);
+ insFlags &= ~OPFLAG_PREFORMAT;
+ }else
+#endif
+ {
+ sqlite3VdbeAddOp3(v, OP_RowCell, iDest, iSrc, regRowid);
+ }
+ sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid);
+ if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){
+ sqlite3VdbeChangeP4(v, -1, (char*)pDest, P4_TABLE);
}
- sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1);
- sqlite3VdbeAddOp4(v, OP_Insert, iDest, regData, regRowid,
- (char*)pDest, P4_TABLE);
sqlite3VdbeChangeP5(v, insFlags);
+
sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v);
sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
@@ -123398,13 +126081,22 @@ static int xferOptimization(
if( sqlite3_stricmp(sqlite3StrBINARY, zColl) ) break;
}
if( i==pSrcIdx->nColumn ){
- idxInsFlags = OPFLAG_USESEEKRESULT;
+ idxInsFlags = OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT;
sqlite3VdbeAddOp1(v, OP_SeekEnd, iDest);
+ sqlite3VdbeAddOp2(v, OP_RowCell, iDest, iSrc);
}
}else if( !HasRowid(pSrc) && pDestIdx->idxType==SQLITE_IDXTYPE_PRIMARYKEY ){
idxInsFlags |= OPFLAG_NCHANGE;
}
- sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1);
+ if( idxInsFlags!=(OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT) ){
+ sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1);
+ if( (db->mDbFlags & DBFLAG_Vacuum)==0
+ && !HasRowid(pDest)
+ && IsPrimaryKeyIndex(pDestIdx)
+ ){
+ codeWithoutRowidPreupdate(pParse, pDest, iDest, regData);
+ }
+ }
sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
sqlite3VdbeChangeP5(v, idxInsFlags|OPFLAG_APPEND);
sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); VdbeCoverage(v);
@@ -123930,6 +126622,8 @@ struct sqlite3_api_routines {
int,const char**);
void (*free_filename)(char*);
sqlite3_file *(*database_file_object)(const char*);
+ /* Version 3.34.0 and later */
+ int (*txn_state)(sqlite3*,const char*);
};
/*
@@ -124234,6 +126928,8 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_create_filename sqlite3_api->create_filename
#define sqlite3_free_filename sqlite3_api->free_filename
#define sqlite3_database_file_object sqlite3_api->database_file_object
+/* Version 3.34.0 and later */
+#define sqlite3_txn_state sqlite3_api->txn_state
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
@@ -124716,6 +127412,8 @@ static const sqlite3_api_routines sqlite3Apis = {
sqlite3_create_filename,
sqlite3_free_filename,
sqlite3_database_file_object,
+ /* Version 3.34.0 and later */
+ sqlite3_txn_state,
};
/* True if x is the directory separator character
@@ -124751,7 +127449,7 @@ static int sqlite3LoadExtension(
const char *zEntry;
char *zAltEntry = 0;
void **aHandle;
- u64 nMsg = 300 + sqlite3Strlen30(zFile);
+ u64 nMsg = strlen(zFile);
int ii;
int rc;
@@ -124785,6 +127483,12 @@ static int sqlite3LoadExtension(
zEntry = zProc ? zProc : "sqlite3_extension_init";
+ /* tag-20210611-1. Some dlopen() implementations will segfault if given
+ ** an oversize filename. Most filesystems have a pathname limit of 4K,
+ ** so limit the extension filename length to about twice that.
+ ** https://sqlite.org/forum/forumpost/08a0d6d9bf */
+ if( nMsg>SQLITE_MAX_PATHLEN ) goto extension_not_found;
+
handle = sqlite3OsDlOpen(pVfs, zFile);
#if SQLITE_OS_UNIX || SQLITE_OS_WIN
for(ii=0; ii<ArraySize(azEndings) && handle==0; ii++){
@@ -124794,17 +127498,7 @@ static int sqlite3LoadExtension(
sqlite3_free(zAltFile);
}
#endif
- if( handle==0 ){
- if( pzErrMsg ){
- *pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg);
- if( zErrmsg ){
- sqlite3_snprintf(nMsg, zErrmsg,
- "unable to open shared library [%s]", zFile);
- sqlite3OsDlError(pVfs, nMsg-1, zErrmsg);
- }
- }
- return SQLITE_ERROR;
- }
+ if( handle==0 ) goto extension_not_found;
xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry);
/* If no entry point was specified and the default legacy
@@ -124841,10 +127535,11 @@ static int sqlite3LoadExtension(
}
if( xInit==0 ){
if( pzErrMsg ){
- nMsg += sqlite3Strlen30(zEntry);
+ nMsg += strlen(zEntry) + 300;
*pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg);
if( zErrmsg ){
- sqlite3_snprintf(nMsg, zErrmsg,
+ assert( nMsg<0x7fffffff ); /* zErrmsg would be NULL if not so */
+ sqlite3_snprintf((int)nMsg, zErrmsg,
"no entry point [%s] in shared library [%s]", zEntry, zFile);
sqlite3OsDlError(pVfs, nMsg-1, zErrmsg);
}
@@ -124878,6 +127573,19 @@ static int sqlite3LoadExtension(
db->aExtension[db->nExtension++] = handle;
return SQLITE_OK;
+
+extension_not_found:
+ if( pzErrMsg ){
+ nMsg += 300;
+ *pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg);
+ if( zErrmsg ){
+ assert( nMsg<0x7fffffff ); /* zErrmsg would be NULL if not so */
+ sqlite3_snprintf((int)nMsg, zErrmsg,
+ "unable to open shared library [%.*s]", SQLITE_MAX_PATHLEN, zFile);
+ sqlite3OsDlError(pVfs, nMsg-1, zErrmsg);
+ }
+ }
+ return SQLITE_ERROR;
}
SQLITE_API int sqlite3_load_extension(
sqlite3 *db, /* Load the extension into this database connection */
@@ -125876,7 +128584,9 @@ static int getTempStore(const char *z){
static int invalidateTempStorage(Parse *pParse){
sqlite3 *db = pParse->db;
if( db->aDb[1].pBt!=0 ){
- if( !db->autoCommit || sqlite3BtreeIsInReadTrans(db->aDb[1].pBt) ){
+ if( !db->autoCommit
+ || sqlite3BtreeTxnState(db->aDb[1].pBt)!=SQLITE_TXN_NONE
+ ){
sqlite3ErrorMsg(pParse, "temporary storage cannot be changed "
"from within a transaction");
return SQLITE_ERROR;
@@ -127196,7 +129906,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
aiCols = 0;
if( pParent ){
x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, &aiCols);
- assert( x==0 );
+ assert( x==0 || db->mallocFailed );
}
addrOk = sqlite3VdbeMakeLabel(pParse);
@@ -127221,7 +129931,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
int jmp = sqlite3VdbeCurrentAddr(v)+2;
sqlite3VdbeAddOp3(v, OP_SeekRowid, i, jmp, regRow); VdbeCoverage(v);
sqlite3VdbeGoto(v, addrOk);
- assert( pFK->nCol==1 );
+ assert( pFK->nCol==1 || db->mallocFailed );
}
/* Generate code to report an FK violation to the caller. */
@@ -127712,7 +130422,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
** Checkpoint the database.
*/
case PragTyp_WAL_CHECKPOINT: {
- int iBt = (pId2->z?iDb:SQLITE_MAX_ATTACHED);
+ int iBt = (pId2->z?iDb:SQLITE_MAX_DB);
int eMode = SQLITE_CHECKPOINT_PASSIVE;
if( zRight ){
if( sqlite3StrICmp(zRight, "full")==0 ){
@@ -128360,7 +131070,7 @@ SQLITE_PRIVATE Module *sqlite3PragmaVtabRegister(sqlite3 *db, const char *zName)
*/
static void corruptSchema(
InitData *pData, /* Initialization context */
- const char *zObj, /* Object being parsed at the point of error */
+ char **azObj, /* Type and name of object being parsed */
const char *zExtra /* Error information */
){
sqlite3 *db = pData->db;
@@ -128368,14 +131078,18 @@ static void corruptSchema(
pData->rc = SQLITE_NOMEM_BKPT;
}else if( pData->pzErrMsg[0]!=0 ){
/* A error message has already been generated. Do not overwrite it */
- }else if( pData->mInitFlags & INITFLAG_AlterTable ){
- *pData->pzErrMsg = sqlite3DbStrDup(db, zExtra);
+ }else if( pData->mInitFlags & (INITFLAG_AlterRename|INITFLAG_AlterDrop) ){
+ *pData->pzErrMsg = sqlite3MPrintf(db,
+ "error in %s %s after %s: %s", azObj[0], azObj[1],
+ (pData->mInitFlags & INITFLAG_AlterRename) ? "rename" : "drop column",
+ zExtra
+ );
pData->rc = SQLITE_ERROR;
}else if( db->flags & SQLITE_WriteSchema ){
pData->rc = SQLITE_CORRUPT_BKPT;
}else{
char *z;
- if( zObj==0 ) zObj = "?";
+ const char *zObj = azObj[1] ? azObj[1] : "?";
z = sqlite3MPrintf(db, "malformed database schema (%s)", zObj);
if( zExtra && zExtra[0] ) z = sqlite3MPrintf(db, "%z - %s", z, zExtra);
*pData->pzErrMsg = z;
@@ -128431,21 +131145,28 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char
UNUSED_PARAMETER2(NotUsed, argc);
assert( sqlite3_mutex_held(db->mutex) );
db->mDbFlags |= DBFLAG_EncodingFixed;
+ if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */
pData->nInitRow++;
if( db->mallocFailed ){
- corruptSchema(pData, argv[1], 0);
+ corruptSchema(pData, argv, 0);
return 1;
}
assert( iDb>=0 && iDb<db->nDb );
- if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */
if( argv[3]==0 ){
- corruptSchema(pData, argv[1], 0);
- }else if( sqlite3_strnicmp(argv[4],"create ",7)==0 ){
+ corruptSchema(pData, argv, 0);
+ }else if( argv[4]
+ && 'c'==sqlite3UpperToLower[(unsigned char)argv[4][0]]
+ && 'r'==sqlite3UpperToLower[(unsigned char)argv[4][1]] ){
/* Call the parser to process a CREATE TABLE, INDEX or VIEW.
** But because db->init.busy is set to 1, no VDBE code is generated
** or executed. All the parser does is build the internal data
** structures that describe the table, index, or view.
+ **
+ ** No other valid SQL statement, other than the variable CREATE statements,
+ ** can begin with the letters "C" and "R". Thus, it is not possible run
+ ** any other kind of statement while parsing the schema, even a corrupt
+ ** schema.
*/
int rc;
u8 saved_iDb = db->init.iDb;
@@ -128458,7 +131179,7 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char
|| (db->init.newTnum>pData->mxPage && pData->mxPage>0)
){
if( sqlite3Config.bExtraSchemaChecks ){
- corruptSchema(pData, argv[1], "invalid rootpage");
+ corruptSchema(pData, argv, "invalid rootpage");
}
}
db->init.orphanTrigger = 0;
@@ -128477,13 +131198,13 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char
if( rc==SQLITE_NOMEM ){
sqlite3OomFault(db);
}else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED ){
- corruptSchema(pData, argv[1], sqlite3_errmsg(db));
+ corruptSchema(pData, argv, sqlite3_errmsg(db));
}
}
}
sqlite3_finalize(pStmt);
}else if( argv[1]==0 || (argv[4]!=0 && argv[4][0]!=0) ){
- corruptSchema(pData, argv[1], 0);
+ corruptSchema(pData, argv, 0);
}else{
/* If the SQL column is blank it means this is an index that
** was created to be the PRIMARY KEY or to fulfill a UNIQUE
@@ -128494,7 +131215,7 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char
Index *pIndex;
pIndex = sqlite3FindIndex(db, argv[1], db->aDb[iDb].zDbSName);
if( pIndex==0 ){
- corruptSchema(pData, argv[1], "orphan index");
+ corruptSchema(pData, argv, "orphan index");
}else
if( sqlite3GetUInt32(argv[3],&pIndex->tnum)==0
|| pIndex->tnum<2
@@ -128502,7 +131223,7 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char
|| sqlite3IndexHasDuplicateRootPage(pIndex)
){
if( sqlite3Config.bExtraSchemaChecks ){
- corruptSchema(pData, argv[1], "invalid rootpage");
+ corruptSchema(pData, argv, "invalid rootpage");
}
}
}
@@ -128579,7 +131300,7 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl
** on the b-tree database, open one now. If a transaction is opened, it
** will be closed before this function returns. */
sqlite3BtreeEnter(pDb->pBt);
- if( !sqlite3BtreeIsInReadTrans(pDb->pBt) ){
+ if( sqlite3BtreeTxnState(pDb->pBt)==SQLITE_TXN_NONE ){
rc = sqlite3BtreeBeginTrans(pDb->pBt, 0, 0);
if( rc!=SQLITE_OK ){
sqlite3SetString(pzErrMsg, db, sqlite3ErrStr(rc));
@@ -128705,18 +131426,22 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl
}
#endif
}
+ assert( pDb == &(db->aDb[iDb]) );
if( db->mallocFailed ){
rc = SQLITE_NOMEM_BKPT;
sqlite3ResetAllSchemasOfConnection(db);
- }
+ pDb = &db->aDb[iDb];
+ }else
if( rc==SQLITE_OK || (db->flags&SQLITE_NoSchemaError)){
- /* Black magic: If the SQLITE_NoSchemaError flag is set, then consider
- ** the schema loaded, even if errors occurred. In this situation the
- ** current sqlite3_prepare() operation will fail, but the following one
- ** will attempt to compile the supplied statement against whatever subset
- ** of the schema was loaded before the error occurred. The primary
- ** purpose of this is to allow access to the sqlite_schema table
- ** even when its contents have been corrupted.
+ /* Hack: If the SQLITE_NoSchemaError flag is set, then consider
+ ** the schema loaded, even if errors (other than OOM) occurred. In
+ ** this situation the current sqlite3_prepare() operation will fail,
+ ** but the following one will attempt to compile the supplied statement
+ ** against whatever subset of the schema was loaded before the error
+ ** occurred.
+ **
+ ** The primary purpose of this is to allow access to the sqlite_schema
+ ** table even when its contents have been corrupted.
*/
DbSetProperty(db, iDb, DB_SchemaLoaded);
rc = SQLITE_OK;
@@ -128822,10 +131547,11 @@ static void schemaIsValid(Parse *pParse){
/* If there is not already a read-only (or read-write) transaction opened
** on the b-tree database, open one now. If a transaction is opened, it
** will be closed immediately after reading the meta-value. */
- if( !sqlite3BtreeIsInReadTrans(pBt) ){
+ if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_NONE ){
rc = sqlite3BtreeBeginTrans(pBt, 0, 0);
if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
sqlite3OomFault(db);
+ pParse->rc = SQLITE_NOMEM;
}
if( rc!=SQLITE_OK ) return;
openedTransaction = 1;
@@ -128883,27 +131609,20 @@ SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){
}
/*
-** Deallocate a single AggInfo object
-*/
-static void agginfoFree(sqlite3 *db, AggInfo *p){
- sqlite3DbFree(db, p->aCol);
- sqlite3DbFree(db, p->aFunc);
- sqlite3DbFree(db, p);
-}
-
-/*
** Free all memory allocations in the pParse object
*/
SQLITE_PRIVATE void sqlite3ParserReset(Parse *pParse){
sqlite3 *db = pParse->db;
- AggInfo *pThis = pParse->pAggList;
- while( pThis ){
- AggInfo *pNext = pThis->pNext;
- agginfoFree(db, pThis);
- pThis = pNext;
+ while( pParse->pCleanup ){
+ ParseCleanup *pCleanup = pParse->pCleanup;
+ pParse->pCleanup = pCleanup->pNext;
+ pCleanup->xCleanup(db, pCleanup->pPtr);
+ sqlite3DbFreeNN(db, pCleanup);
}
sqlite3DbFree(db, pParse->aLabel);
- sqlite3ExprListDelete(db, pParse->pConstExpr);
+ if( pParse->pConstExpr ){
+ sqlite3ExprListDelete(db, pParse->pConstExpr);
+ }
if( db ){
assert( db->lookaside.bDisable >= pParse->disableLookaside );
db->lookaside.bDisable -= pParse->disableLookaside;
@@ -128913,6 +131632,55 @@ SQLITE_PRIVATE void sqlite3ParserReset(Parse *pParse){
}
/*
+** Add a new cleanup operation to a Parser. The cleanup should happen when
+** the parser object is destroyed. But, beware: the cleanup might happen
+** immediately.
+**
+** Use this mechanism for uncommon cleanups. There is a higher setup
+** cost for this mechansim (an extra malloc), so it should not be used
+** for common cleanups that happen on most calls. But for less
+** common cleanups, we save a single NULL-pointer comparison in
+** sqlite3ParserReset(), which reduces the total CPU cycle count.
+**
+** If a memory allocation error occurs, then the cleanup happens immediately.
+** When either SQLITE_DEBUG or SQLITE_COVERAGE_TEST are defined, the
+** pParse->earlyCleanup flag is set in that case. Calling code show verify
+** that test cases exist for which this happens, to guard against possible
+** use-after-free errors following an OOM. The preferred way to do this is
+** to immediately follow the call to this routine with:
+**
+** testcase( pParse->earlyCleanup );
+**
+** This routine returns a copy of its pPtr input (the third parameter)
+** except if an early cleanup occurs, in which case it returns NULL. So
+** another way to check for early cleanup is to check the return value.
+** Or, stop using the pPtr parameter with this call and use only its
+** return value thereafter. Something like this:
+**
+** pObj = sqlite3ParserAddCleanup(pParse, destructor, pObj);
+*/
+SQLITE_PRIVATE void *sqlite3ParserAddCleanup(
+ Parse *pParse, /* Destroy when this Parser finishes */
+ void (*xCleanup)(sqlite3*,void*), /* The cleanup routine */
+ void *pPtr /* Pointer to object to be cleaned up */
+){
+ ParseCleanup *pCleanup = sqlite3DbMallocRaw(pParse->db, sizeof(*pCleanup));
+ if( pCleanup ){
+ pCleanup->pNext = pParse->pCleanup;
+ pParse->pCleanup = pCleanup;
+ pCleanup->pPtr = pPtr;
+ pCleanup->xCleanup = xCleanup;
+ }else{
+ xCleanup(pParse->db, pPtr);
+ pPtr = 0;
+#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
+ pParse->earlyCleanup = 1;
+#endif
+ }
+ return pPtr;
+}
+
+/*
** Compile the UTF-8 encoded SQL statement zSql into a statement handle.
*/
static int sqlite3Prepare(
@@ -129010,12 +131778,6 @@ static int sqlite3Prepare(
}
assert( 0==sParse.nQueryLoop );
- if( sParse.rc==SQLITE_DONE ){
- sParse.rc = SQLITE_OK;
- }
- if( sParse.checkSchema ){
- schemaIsValid(&sParse);
- }
if( pzTail ){
*pzTail = sParse.zTail;
}
@@ -129025,21 +131787,30 @@ static int sqlite3Prepare(
}
if( db->mallocFailed ){
sParse.rc = SQLITE_NOMEM_BKPT;
+ sParse.checkSchema = 0;
}
- rc = sParse.rc;
- if( rc!=SQLITE_OK ){
- if( sParse.pVdbe ) sqlite3VdbeFinalize(sParse.pVdbe);
- assert(!(*ppStmt));
+ if( sParse.rc!=SQLITE_OK && sParse.rc!=SQLITE_DONE ){
+ if( sParse.checkSchema ){
+ schemaIsValid(&sParse);
+ }
+ if( sParse.pVdbe ){
+ sqlite3VdbeFinalize(sParse.pVdbe);
+ }
+ assert( 0==(*ppStmt) );
+ rc = sParse.rc;
+ if( zErrMsg ){
+ sqlite3ErrorWithMsg(db, rc, "%s", zErrMsg);
+ sqlite3DbFree(db, zErrMsg);
+ }else{
+ sqlite3Error(db, rc);
+ }
}else{
+ assert( zErrMsg==0 );
*ppStmt = (sqlite3_stmt*)sParse.pVdbe;
+ rc = SQLITE_OK;
+ sqlite3ErrorClear(db);
}
- if( zErrMsg ){
- sqlite3ErrorWithMsg(db, rc, "%s", zErrMsg);
- sqlite3DbFree(db, zErrMsg);
- }else{
- sqlite3Error(db, rc);
- }
/* Delete any TriggerPrg structures allocated while parsing this statement. */
while( sParse.pTriggerPrg ){
@@ -129085,6 +131856,7 @@ static int sqlite3LockAndPrepare(
sqlite3BtreeLeaveAll(db);
rc = sqlite3ApiExit(db, rc);
assert( (rc&db->errMask)==rc );
+ db->busyHandler.nBusy = 0;
sqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -129384,12 +132156,16 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){
sqlite3ExprDelete(db, p->pHaving);
sqlite3ExprListDelete(db, p->pOrderBy);
sqlite3ExprDelete(db, p->pLimit);
+ if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith);
#ifndef SQLITE_OMIT_WINDOWFUNC
if( OK_IF_ALWAYS_TRUE(p->pWinDefn) ){
sqlite3WindowListDelete(db, p->pWinDefn);
}
+ while( p->pWin ){
+ assert( p->pWin->ppThis==&p->pWin );
+ sqlite3WindowUnlinkFromSelect(p->pWin);
+ }
#endif
- if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith);
if( bFree ) sqlite3DbFreeNN(db, p);
p = pPrior;
bFree = 1;
@@ -129561,7 +132337,7 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p
** Return the index of a column in a table. Return -1 if the column
** is not contained in the table.
*/
-static int columnIndex(Table *pTab, const char *zCol){
+SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol){
int i;
u8 h = sqlite3StrIHash(zCol);
Column *pCol;
@@ -129593,7 +132369,7 @@ static int tableAndColumnIndex(
assert( (piTab==0)==(piCol==0) ); /* Both or neither are NULL */
for(i=0; i<N; i++){
- iCol = columnIndex(pSrc->a[i].pTab, zCol);
+ iCol = sqlite3ColumnIndex(pSrc->a[i].pTab, zCol);
if( iCol>=0
&& (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pTab->aCol[iCol])==0)
){
@@ -129646,7 +132422,7 @@ static void addWhereTerm(
ExprSetProperty(pEq, EP_FromJoin);
assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) );
ExprSetVVAProperty(pEq, EP_NoReduce);
- pEq->iRightJoinTable = (i16)pE2->iTable;
+ pEq->iRightJoinTable = pE2->iTable;
}
*ppWhere = sqlite3ExprAnd(pParse, *ppWhere, pEq);
}
@@ -129682,7 +132458,7 @@ SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr *p, int iTable){
ExprSetProperty(p, EP_FromJoin);
assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) );
ExprSetVVAProperty(p, EP_NoReduce);
- p->iRightJoinTable = (i16)iTable;
+ p->iRightJoinTable = iTable;
if( p->op==TK_FUNCTION && p->x.pList ){
int i;
for(i=0; i<p->x.pList->nExpr; i++){
@@ -129706,6 +132482,9 @@ static void unsetJoinExpr(Expr *p, int iTable){
&& (iTable<0 || p->iRightJoinTable==iTable) ){
ExprClearProperty(p, EP_FromJoin);
}
+ if( p->op==TK_COLUMN && p->iTable==iTable ){
+ ExprClearProperty(p, EP_CanBeNull);
+ }
if( p->op==TK_FUNCTION && p->x.pList ){
int i;
for(i=0; i<p->x.pList->nExpr; i++){
@@ -129734,8 +132513,8 @@ static void unsetJoinExpr(Expr *p, int iTable){
static int sqliteProcessJoin(Parse *pParse, Select *p){
SrcList *pSrc; /* All tables in the FROM clause */
int i, j; /* Loop counters */
- struct SrcList_item *pLeft; /* Left table being joined */
- struct SrcList_item *pRight; /* Right table being joined */
+ SrcItem *pLeft; /* Left table being joined */
+ SrcItem *pRight; /* Right table being joined */
pSrc = p->pSrc;
pLeft = &pSrc->a[0];
@@ -129803,7 +132582,7 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){
int iRightCol; /* Column number of matching column on the right */
zName = pList->a[j].zName;
- iRightCol = columnIndex(pRightTab, zName);
+ iRightCol = sqlite3ColumnIndex(pRightTab, zName);
if( iRightCol<0
|| !tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 0)
){
@@ -130033,31 +132812,155 @@ static void codeOffset(
}
/*
-** Add code that will check to make sure the N registers starting at iMem
-** form a distinct entry. iTab is a sorting index that holds previously
-** seen combinations of the N values. A new entry is made in iTab
-** if the current N values are new.
+** Add code that will check to make sure the array of registers starting at
+** iMem form a distinct entry. This is used by both "SELECT DISTINCT ..." and
+** distinct aggregates ("SELECT count(DISTINCT <expr>) ..."). Three strategies
+** are available. Which is used depends on the value of parameter eTnctType,
+** as follows:
**
-** A jump to addrRepeat is made and the N+1 values are popped from the
-** stack if the top N elements are not distinct.
-*/
-static void codeDistinct(
+** WHERE_DISTINCT_UNORDERED/WHERE_DISTINCT_NOOP:
+** Build an ephemeral table that contains all entries seen before and
+** skip entries which have been seen before.
+**
+** Parameter iTab is the cursor number of an ephemeral table that must
+** be opened before the VM code generated by this routine is executed.
+** The ephemeral cursor table is queried for a record identical to the
+** record formed by the current array of registers. If one is found,
+** jump to VM address addrRepeat. Otherwise, insert a new record into
+** the ephemeral cursor and proceed.
+**
+** The returned value in this case is a copy of parameter iTab.
+**
+** WHERE_DISTINCT_ORDERED:
+** In this case rows are being delivered sorted order. The ephermal
+** table is not required. Instead, the current set of values
+** is compared against previous row. If they match, the new row
+** is not distinct and control jumps to VM address addrRepeat. Otherwise,
+** the VM program proceeds with processing the new row.
+**
+** The returned value in this case is the register number of the first
+** in an array of registers used to store the previous result row so that
+** it can be compared to the next. The caller must ensure that this
+** register is initialized to NULL. (The fixDistinctOpenEph() routine
+** will take care of this initialization.)
+**
+** WHERE_DISTINCT_UNIQUE:
+** In this case it has already been determined that the rows are distinct.
+** No special action is required. The return value is zero.
+**
+** Parameter pEList is the list of expressions used to generated the
+** contents of each row. It is used by this routine to determine (a)
+** how many elements there are in the array of registers and (b) the
+** collation sequences that should be used for the comparisons if
+** eTnctType is WHERE_DISTINCT_ORDERED.
+*/
+static int codeDistinct(
Parse *pParse, /* Parsing and code generating context */
+ int eTnctType, /* WHERE_DISTINCT_* value */
int iTab, /* A sorting index used to test for distinctness */
int addrRepeat, /* Jump to here if not distinct */
- int N, /* Number of elements */
- int iMem /* First element */
+ ExprList *pEList, /* Expression for each element */
+ int regElem /* First element */
){
- Vdbe *v;
- int r1;
+ int iRet = 0;
+ int nResultCol = pEList->nExpr;
+ Vdbe *v = pParse->pVdbe;
- v = pParse->pVdbe;
- r1 = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N); VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1);
- sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, iMem, N);
- sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
- sqlite3ReleaseTempReg(pParse, r1);
+ switch( eTnctType ){
+ case WHERE_DISTINCT_ORDERED: {
+ int i;
+ int iJump; /* Jump destination */
+ int regPrev; /* Previous row content */
+
+ /* Allocate space for the previous row */
+ iRet = regPrev = pParse->nMem+1;
+ pParse->nMem += nResultCol;
+
+ iJump = sqlite3VdbeCurrentAddr(v) + nResultCol;
+ for(i=0; i<nResultCol; i++){
+ CollSeq *pColl = sqlite3ExprCollSeq(pParse, pEList->a[i].pExpr);
+ if( i<nResultCol-1 ){
+ sqlite3VdbeAddOp3(v, OP_Ne, regElem+i, iJump, regPrev+i);
+ VdbeCoverage(v);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_Eq, regElem+i, addrRepeat, regPrev+i);
+ VdbeCoverage(v);
+ }
+ sqlite3VdbeChangeP4(v, -1, (const char *)pColl, P4_COLLSEQ);
+ sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
+ }
+ assert( sqlite3VdbeCurrentAddr(v)==iJump || pParse->db->mallocFailed );
+ sqlite3VdbeAddOp3(v, OP_Copy, regElem, regPrev, nResultCol-1);
+ break;
+ }
+
+ case WHERE_DISTINCT_UNIQUE: {
+ /* nothing to do */
+ break;
+ }
+
+ default: {
+ int r1 = sqlite3GetTempReg(pParse);
+ sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, regElem, nResultCol);
+ VdbeCoverage(v);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regElem, nResultCol, r1);
+ sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, regElem, nResultCol);
+ sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
+ sqlite3ReleaseTempReg(pParse, r1);
+ iRet = iTab;
+ break;
+ }
+ }
+
+ return iRet;
+}
+
+/*
+** This routine runs after codeDistinct(). It makes necessary
+** adjustments to the OP_OpenEphemeral opcode that the codeDistinct()
+** routine made use of. This processing must be done separately since
+** sometimes codeDistinct is called before the OP_OpenEphemeral is actually
+** laid down.
+**
+** WHERE_DISTINCT_NOOP:
+** WHERE_DISTINCT_UNORDERED:
+**
+** No adjustments necessary. This function is a no-op.
+**
+** WHERE_DISTINCT_UNIQUE:
+**
+** The ephemeral table is not needed. So change the
+** OP_OpenEphemeral opcode into an OP_Noop.
+**
+** WHERE_DISTINCT_ORDERED:
+**
+** The ephemeral table is not needed. But we do need register
+** iVal to be initialized to NULL. So change the OP_OpenEphemeral
+** into an OP_Null on the iVal register.
+*/
+static void fixDistinctOpenEph(
+ Parse *pParse, /* Parsing and code generating context */
+ int eTnctType, /* WHERE_DISTINCT_* value */
+ int iVal, /* Value returned by codeDistinct() */
+ int iOpenEphAddr /* Address of OP_OpenEphemeral instruction for iTab */
+){
+ if( eTnctType==WHERE_DISTINCT_UNIQUE || eTnctType==WHERE_DISTINCT_ORDERED ){
+ Vdbe *v = pParse->pVdbe;
+ sqlite3VdbeChangeToNoop(v, iOpenEphAddr);
+ if( sqlite3VdbeGetOp(v, iOpenEphAddr+1)->opcode==OP_Explain ){
+ sqlite3VdbeChangeToNoop(v, iOpenEphAddr+1);
+ }
+ if( eTnctType==WHERE_DISTINCT_ORDERED ){
+ /* Change the OP_OpenEphemeral to an OP_Null that sets the MEM_Cleared
+ ** bit on the first register of the previous value. This will cause the
+ ** OP_Ne added in codeDistinct() to always fail on the first iteration of
+ ** the loop even if the first row is all NULLs. */
+ VdbeOp *pOp = sqlite3VdbeGetOp(v, iOpenEphAddr);
+ pOp->opcode = OP_Null;
+ pOp->p1 = 1;
+ pOp->p2 = iVal;
+ }
+ }
}
#ifdef SQLITE_ENABLE_SORTER_REFERENCES
@@ -130305,59 +133208,11 @@ static void selectInnerLoop(
** part of the result.
*/
if( hasDistinct ){
- switch( pDistinct->eTnctType ){
- case WHERE_DISTINCT_ORDERED: {
- VdbeOp *pOp; /* No longer required OpenEphemeral instr. */
- int iJump; /* Jump destination */
- int regPrev; /* Previous row content */
-
- /* Allocate space for the previous row */
- regPrev = pParse->nMem+1;
- pParse->nMem += nResultCol;
-
- /* Change the OP_OpenEphemeral coded earlier to an OP_Null
- ** sets the MEM_Cleared bit on the first register of the
- ** previous value. This will cause the OP_Ne below to always
- ** fail on the first iteration of the loop even if the first
- ** row is all NULLs.
- */
- sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct);
- pOp = sqlite3VdbeGetOp(v, pDistinct->addrTnct);
- pOp->opcode = OP_Null;
- pOp->p1 = 1;
- pOp->p2 = regPrev;
- pOp = 0; /* Ensure pOp is not used after sqlite3VdbeAddOp() */
-
- iJump = sqlite3VdbeCurrentAddr(v) + nResultCol;
- for(i=0; i<nResultCol; i++){
- CollSeq *pColl = sqlite3ExprCollSeq(pParse, p->pEList->a[i].pExpr);
- if( i<nResultCol-1 ){
- sqlite3VdbeAddOp3(v, OP_Ne, regResult+i, iJump, regPrev+i);
- VdbeCoverage(v);
- }else{
- sqlite3VdbeAddOp3(v, OP_Eq, regResult+i, iContinue, regPrev+i);
- VdbeCoverage(v);
- }
- sqlite3VdbeChangeP4(v, -1, (const char *)pColl, P4_COLLSEQ);
- sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
- }
- assert( sqlite3VdbeCurrentAddr(v)==iJump || pParse->db->mallocFailed );
- sqlite3VdbeAddOp3(v, OP_Copy, regResult, regPrev, nResultCol-1);
- break;
- }
-
- case WHERE_DISTINCT_UNIQUE: {
- sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct);
- break;
- }
-
- default: {
- assert( pDistinct->eTnctType==WHERE_DISTINCT_UNORDERED );
- codeDistinct(pParse, pDistinct->tabTnct, iContinue, nResultCol,
- regResult);
- break;
- }
- }
+ int eType = pDistinct->eTnctType;
+ int iTab = pDistinct->tabTnct;
+ assert( nResultCol==p->pEList->nExpr );
+ iTab = codeDistinct(pParse, eType, iTab, iContinue, p->pEList, regResult);
+ fixDistinctOpenEph(pParse, eType, iTab, pDistinct->addrTnct);
if( pSort==0 ){
codeOffset(v, p->iOffset, iContinue);
}
@@ -130682,7 +133537,7 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoFromExprList(
/*
** Name of the connection operator, used for error messages.
*/
-static const char *selectOpName(int id){
+SQLITE_PRIVATE const char *sqlite3SelectOpName(int id){
char *z;
switch( id ){
case TK_ALL: z = "UNION ALL"; break;
@@ -131023,7 +133878,13 @@ static const char *columnTypeImpl(
** of the SELECT statement. Return the declaration type and origin
** data for the result-set column of the sub-select.
*/
- if( iCol>=0 && iCol<pS->pEList->nExpr ){
+ if( iCol<pS->pEList->nExpr
+#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
+ && iCol>=0
+#else
+ && ALWAYS(iCol>=0)
+#endif
+ ){
/* If iCol is less than zero, then the expression requests the
** rowid of the sub-select or view. This expression is legal (see
** test case misc2.2.2) - it always evaluates to NULL.
@@ -131165,7 +134026,7 @@ static void generateColumnTypes(
** then the result column name with the table name
** prefix, ex: TABLE.COLUMN. Otherwise use zSpan.
*/
-static void generateColumnNames(
+SQLITE_PRIVATE void sqlite3GenerateColumnNames(
Parse *pParse, /* Parser context */
Select *pSelect /* Generate column names for this SELECT statement */
){
@@ -131255,7 +134116,7 @@ static void generateColumnNames(
** and will break if those assumptions changes. Hence, use extreme caution
** when modifying this routine to avoid breaking legacy.
**
-** See Also: generateColumnNames()
+** See Also: sqlite3GenerateColumnNames()
*/
SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
Parse *pParse, /* Parsing context */
@@ -131271,13 +134132,14 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
char *zName; /* Column name */
int nName; /* Size of name in zName[] */
Hash ht; /* Hash table of column names */
+ Table *pTab;
sqlite3HashInit(&ht);
if( pEList ){
nCol = pEList->nExpr;
aCol = sqlite3DbMallocZero(db, sizeof(aCol[0])*nCol);
testcase( aCol==0 );
- if( nCol>32767 ) nCol = 32767;
+ if( NEVER(nCol>32767) ) nCol = 32767;
}else{
nCol = 0;
aCol = 0;
@@ -131293,15 +134155,13 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
/* If the column contains an "AS <name>" phrase, use <name> as the name */
}else{
Expr *pColExpr = sqlite3ExprSkipCollateAndLikely(pEList->a[i].pExpr);
- while( pColExpr->op==TK_DOT ){
+ while( ALWAYS(pColExpr!=0) && pColExpr->op==TK_DOT ){
pColExpr = pColExpr->pRight;
assert( pColExpr!=0 );
}
- if( pColExpr->op==TK_COLUMN ){
+ if( pColExpr->op==TK_COLUMN && (pTab = pColExpr->y.pTab)!=0 ){
/* For columns use the column name name */
int iCol = pColExpr->iColumn;
- Table *pTab = pColExpr->y.pTab;
- assert( pTab!=0 );
if( iCol<0 ) iCol = pTab->iPKey;
zName = iCol>=0 ? pTab->aCol[iCol].zName : "rowid";
}else if( pColExpr->op==TK_ID ){
@@ -131386,6 +134246,7 @@ SQLITE_PRIVATE void sqlite3SelectAddColumnTypeAndCollation(
for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){
const char *zType;
int n, m;
+ pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT);
p = a[i].pExpr;
zType = columnType(&sNC, p, 0, 0, 0);
/* pCol->szEst = ... // Column size est for SELECT tables never used */
@@ -131639,6 +134500,7 @@ static void generateWithRecursiveQuery(
int nCol = p->pEList->nExpr; /* Number of columns in the recursive table */
Vdbe *v = pParse->pVdbe; /* The prepared statement under construction */
Select *pSetup = p->pPrior; /* The setup query */
+ Select *pFirstRec; /* Left-most recursive term */
int addrTop; /* Top of the loop */
int addrCont, addrBreak; /* CONTINUE and BREAK addresses */
int iCurrent = 0; /* The Current table */
@@ -131714,7 +134576,25 @@ static void generateWithRecursiveQuery(
/* Detach the ORDER BY clause from the compound SELECT */
p->pOrderBy = 0;
+ /* Figure out how many elements of the compound SELECT are part of the
+ ** recursive query. Make sure no recursive elements use aggregate
+ ** functions. Mark the recursive elements as UNION ALL even if they
+ ** are really UNION because the distinctness will be enforced by the
+ ** iDistinct table. pFirstRec is left pointing to the left-most
+ ** recursive term of the CTE.
+ */
+ pFirstRec = p;
+ for(pFirstRec=p; ALWAYS(pFirstRec!=0); pFirstRec=pFirstRec->pPrior){
+ if( pFirstRec->selFlags & SF_Aggregate ){
+ sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported");
+ goto end_of_recursive_query;
+ }
+ pFirstRec->op = TK_ALL;
+ if( (pFirstRec->pPrior->selFlags & SF_Recursive)==0 ) break;
+ }
+
/* Store the results of the setup-query in Queue. */
+ pSetup = pFirstRec->pPrior;
pSetup->pNext = 0;
ExplainQueryPlan((pParse, 1, "SETUP"));
rc = sqlite3Select(pParse, pSetup, &destQueue);
@@ -131747,15 +134627,11 @@ static void generateWithRecursiveQuery(
/* Execute the recursive SELECT taking the single row in Current as
** the value for the recursive-table. Store the results in the Queue.
*/
- if( p->selFlags & SF_Aggregate ){
- sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported");
- }else{
- p->pPrior = 0;
- ExplainQueryPlan((pParse, 1, "RECURSIVE STEP"));
- sqlite3Select(pParse, p, &destQueue);
- assert( p->pPrior==0 );
- p->pPrior = pSetup;
- }
+ pFirstRec->pPrior = 0;
+ ExplainQueryPlan((pParse, 1, "RECURSIVE STEP"));
+ sqlite3Select(pParse, p, &destQueue);
+ assert( pFirstRec->pPrior==0 );
+ pFirstRec->pPrior = pSetup;
/* Keep running the loop until the Queue is empty */
sqlite3VdbeGoto(v, addrTop);
@@ -131825,6 +134701,16 @@ static int multiSelectValues(
}
/*
+** Return true if the SELECT statement which is known to be the recursive
+** part of a recursive CTE still has its anchor terms attached. If the
+** anchor terms have already been removed, then return false.
+*/
+static int hasAnchor(Select *p){
+ while( p && (p->selFlags & SF_Recursive)!=0 ){ p = p->pPrior; }
+ return p!=0;
+}
+
+/*
** This routine is called to process a compound query form from
** two or more separate queries using UNION, UNION ALL, EXCEPT, or
** INTERSECT
@@ -131876,12 +134762,8 @@ static int multiSelect(
db = pParse->db;
pPrior = p->pPrior;
dest = *pDest;
- if( pPrior->pOrderBy || pPrior->pLimit ){
- sqlite3ErrorMsg(pParse,"%s clause should come after %s not before",
- pPrior->pOrderBy!=0 ? "ORDER BY" : "LIMIT", selectOpName(p->op));
- rc = 1;
- goto multi_select_end;
- }
+ assert( pPrior->pOrderBy==0 );
+ assert( pPrior->pLimit==0 );
v = sqlite3GetVdbe(pParse);
assert( v!=0 ); /* The VDBE already created by calling function */
@@ -131909,7 +134791,7 @@ static int multiSelect(
assert( p->pEList->nExpr==pPrior->pEList->nExpr );
#ifndef SQLITE_OMIT_CTE
- if( p->selFlags & SF_Recursive ){
+ if( (p->selFlags & SF_Recursive)!=0 && hasAnchor(p) ){
generateWithRecursiveQuery(pParse, p, &dest);
}else
#endif
@@ -131932,13 +134814,14 @@ static int multiSelect(
switch( p->op ){
case TK_ALL: {
int addr = 0;
- int nLimit;
+ int nLimit = 0; /* Initialize to suppress harmless compiler warning */
assert( !pPrior->pLimit );
pPrior->iLimit = p->iLimit;
pPrior->iOffset = p->iOffset;
pPrior->pLimit = p->pLimit;
+ SELECTTRACE(1, pParse, p, ("multiSelect UNION ALL left...\n"));
rc = sqlite3Select(pParse, pPrior, &dest);
- p->pLimit = 0;
+ pPrior->pLimit = 0;
if( rc ){
goto multi_select_end;
}
@@ -131954,13 +134837,14 @@ static int multiSelect(
}
}
ExplainQueryPlan((pParse, 1, "UNION ALL"));
+ SELECTTRACE(1, pParse, p, ("multiSelect UNION ALL right...\n"));
rc = sqlite3Select(pParse, p, &dest);
testcase( rc!=SQLITE_OK );
pDelete = p->pPrior;
p->pPrior = pPrior;
p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
- if( pPrior->pLimit
- && sqlite3ExprIsInteger(pPrior->pLimit->pLeft, &nLimit)
+ if( p->pLimit
+ && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit)
&& nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit)
){
p->nSelectRow = sqlite3LogEst((u64)nLimit);
@@ -132001,10 +134885,12 @@ static int multiSelect(
assert( p->pEList );
}
+
/* Code the SELECT statements to our left
*/
assert( !pPrior->pOrderBy );
sqlite3SelectDestInit(&uniondest, priorOp, unionTab);
+ SELECTTRACE(1, pParse, p, ("multiSelect EXCEPT/UNION left...\n"));
rc = sqlite3Select(pParse, pPrior, &uniondest);
if( rc ){
goto multi_select_end;
@@ -132023,7 +134909,8 @@ static int multiSelect(
p->pLimit = 0;
uniondest.eDest = op;
ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE",
- selectOpName(p->op)));
+ sqlite3SelectOpName(p->op)));
+ SELECTTRACE(1, pParse, p, ("multiSelect EXCEPT/UNION right...\n"));
rc = sqlite3Select(pParse, p, &uniondest);
testcase( rc!=SQLITE_OK );
assert( p->pOrderBy==0 );
@@ -132084,6 +134971,7 @@ static int multiSelect(
/* Code the SELECTs to our left into temporary table "tab1".
*/
sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1);
+ SELECTTRACE(1, pParse, p, ("multiSelect INTERSECT left...\n"));
rc = sqlite3Select(pParse, pPrior, &intersectdest);
if( rc ){
goto multi_select_end;
@@ -132099,7 +134987,8 @@ static int multiSelect(
p->pLimit = 0;
intersectdest.iSDParm = tab2;
ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE",
- selectOpName(p->op)));
+ sqlite3SelectOpName(p->op)));
+ SELECTTRACE(1, pParse, p, ("multiSelect INTERSECT right...\n"));
rc = sqlite3Select(pParse, p, &intersectdest);
testcase( rc!=SQLITE_OK );
pDelete = p->pPrior;
@@ -132208,7 +135097,8 @@ SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){
sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms");
}else{
sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s"
- " do not have the same number of result columns", selectOpName(p->op));
+ " do not have the same number of result columns",
+ sqlite3SelectOpName(p->op));
}
}
@@ -132305,10 +135195,8 @@ static int generateOutputSubroutine(
** if it is the RHS of a row-value IN operator.
*/
case SRT_Mem: {
- if( pParse->nErr==0 ){
- testcase( pIn->nSdst>1 );
- sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm, pIn->nSdst);
- }
+ testcase( pIn->nSdst>1 );
+ sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm, pIn->nSdst);
/* The LIMIT clause will jump out of the loop for us */
break;
}
@@ -132600,7 +135488,7 @@ static int multiSelectOrderBy(
sqlite3SelectDestInit(&destA, SRT_Coroutine, regAddrA);
sqlite3SelectDestInit(&destB, SRT_Coroutine, regAddrB);
- ExplainQueryPlan((pParse, 1, "MERGE (%s)", selectOpName(p->op)));
+ ExplainQueryPlan((pParse, 1, "MERGE (%s)", sqlite3SelectOpName(p->op)));
/* Generate a coroutine to evaluate the SELECT statement to the
** left of the compound operator - the "A" select.
@@ -132734,6 +135622,9 @@ static int multiSelectOrderBy(
p->pPrior = pPrior;
pPrior->pNext = p;
+ sqlite3ExprListDelete(db, pPrior->pOrderBy);
+ pPrior->pOrderBy = 0;
+
/*** TBD: Insert subroutine calls to close cursors on incomplete
**** subqueries ****/
ExplainQueryPlanPop(pParse);
@@ -132788,9 +135679,12 @@ static Expr *substExpr(
&& pExpr->iTable==pSubst->iTable
&& !ExprHasProperty(pExpr, EP_FixedCol)
){
+#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
if( pExpr->iColumn<0 ){
pExpr->op = TK_NULL;
- }else{
+ }else
+#endif
+ {
Expr *pNew;
Expr *pCopy = pSubst->pEList->a[pExpr->iColumn].pExpr;
Expr ifNullRow;
@@ -132805,32 +135699,33 @@ static Expr *substExpr(
ifNullRow.op = TK_IF_NULL_ROW;
ifNullRow.pLeft = pCopy;
ifNullRow.iTable = pSubst->iNewTable;
- ifNullRow.flags = EP_Skip;
+ ifNullRow.flags = EP_IfNullRow;
pCopy = &ifNullRow;
}
testcase( ExprHasProperty(pCopy, EP_Subquery) );
pNew = sqlite3ExprDup(db, pCopy, 0);
- if( pNew && pSubst->isLeftJoin ){
+ if( db->mallocFailed ){
+ sqlite3ExprDelete(db, pNew);
+ return pExpr;
+ }
+ if( pSubst->isLeftJoin ){
ExprSetProperty(pNew, EP_CanBeNull);
}
- if( pNew && ExprHasProperty(pExpr,EP_FromJoin) ){
- pNew->iRightJoinTable = pExpr->iRightJoinTable;
- ExprSetProperty(pNew, EP_FromJoin);
+ if( ExprHasProperty(pExpr,EP_FromJoin) ){
+ sqlite3SetJoinExpr(pNew, pExpr->iRightJoinTable);
}
sqlite3ExprDelete(db, pExpr);
pExpr = pNew;
/* Ensure that the expression now has an implicit collation sequence,
** just as it did when it was a column of a view or sub-query. */
- if( pExpr ){
- if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE ){
- CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, pExpr);
- pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr,
- (pColl ? pColl->zName : "BINARY")
- );
- }
- ExprClearProperty(pExpr, EP_Collate);
+ if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE ){
+ CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, pExpr);
+ pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr,
+ (pColl ? pColl->zName : "BINARY")
+ );
}
+ ExprClearProperty(pExpr, EP_Collate);
}
}
}else{
@@ -132871,7 +135766,7 @@ static void substSelect(
int doPrior /* Do substitutes on p->pPrior too */
){
SrcList *pSrc;
- struct SrcList_item *pItem;
+ SrcItem *pItem;
int i;
if( !p ) return;
do{
@@ -132901,7 +135796,7 @@ static void substSelect(
** pSrcItem->colUsed mask.
*/
static int recomputeColumnsUsedExpr(Walker *pWalker, Expr *pExpr){
- struct SrcList_item *pItem;
+ SrcItem *pItem;
if( pExpr->op!=TK_COLUMN ) return WRC_Continue;
pItem = pWalker->u.pSrcItem;
if( pItem->iCursor!=pExpr->iTable ) return WRC_Continue;
@@ -132911,7 +135806,7 @@ static int recomputeColumnsUsedExpr(Walker *pWalker, Expr *pExpr){
}
static void recomputeColumnsUsed(
Select *pSelect, /* The complete SELECT statement */
- struct SrcList_item *pSrcItem /* Which FROM clause item to recompute */
+ SrcItem *pSrcItem /* Which FROM clause item to recompute */
){
Walker w;
if( NEVER(pSrcItem->pTab==0) ) return;
@@ -132926,6 +135821,92 @@ static void recomputeColumnsUsed(
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
/*
+** Assign new cursor numbers to each of the items in pSrc. For each
+** new cursor number assigned, set an entry in the aCsrMap[] array
+** to map the old cursor number to the new:
+**
+** aCsrMap[iOld] = iNew;
+**
+** The array is guaranteed by the caller to be large enough for all
+** existing cursor numbers in pSrc.
+**
+** If pSrc contains any sub-selects, call this routine recursively
+** on the FROM clause of each such sub-select, with iExcept set to -1.
+*/
+static void srclistRenumberCursors(
+ Parse *pParse, /* Parse context */
+ int *aCsrMap, /* Array to store cursor mappings in */
+ SrcList *pSrc, /* FROM clause to renumber */
+ int iExcept /* FROM clause item to skip */
+){
+ int i;
+ SrcItem *pItem;
+ for(i=0, pItem=pSrc->a; i<pSrc->nSrc; i++, pItem++){
+ if( i!=iExcept ){
+ Select *p;
+ if( !pItem->fg.isRecursive || aCsrMap[pItem->iCursor]==0 ){
+ aCsrMap[pItem->iCursor] = pParse->nTab++;
+ }
+ pItem->iCursor = aCsrMap[pItem->iCursor];
+ for(p=pItem->pSelect; p; p=p->pPrior){
+ srclistRenumberCursors(pParse, aCsrMap, p->pSrc, -1);
+ }
+ }
+ }
+}
+
+/*
+** Expression walker callback used by renumberCursors() to update
+** Expr objects to match newly assigned cursor numbers.
+*/
+static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){
+ int *aCsrMap = pWalker->u.aiCol;
+ int op = pExpr->op;
+ if( (op==TK_COLUMN || op==TK_IF_NULL_ROW) && aCsrMap[pExpr->iTable] ){
+ pExpr->iTable = aCsrMap[pExpr->iTable];
+ }
+ if( ExprHasProperty(pExpr, EP_FromJoin) && aCsrMap[pExpr->iRightJoinTable] ){
+ pExpr->iRightJoinTable = aCsrMap[pExpr->iRightJoinTable];
+ }
+ return WRC_Continue;
+}
+
+/*
+** Assign a new cursor number to each cursor in the FROM clause (Select.pSrc)
+** of the SELECT statement passed as the second argument, and to each
+** cursor in the FROM clause of any FROM clause sub-selects, recursively.
+** Except, do not assign a new cursor number to the iExcept'th element in
+** the FROM clause of (*p). Update all expressions and other references
+** to refer to the new cursor numbers.
+**
+** Argument aCsrMap is an array that may be used for temporary working
+** space. Two guarantees are made by the caller:
+**
+** * the array is larger than the largest cursor number used within the
+** select statement passed as an argument, and
+**
+** * the array entries for all cursor numbers that do *not* appear in
+** FROM clauses of the select statement as described above are
+** initialized to zero.
+*/
+static void renumberCursors(
+ Parse *pParse, /* Parse context */
+ Select *p, /* Select to renumber cursors within */
+ int iExcept, /* FROM clause item to skip */
+ int *aCsrMap /* Working space */
+){
+ Walker w;
+ srclistRenumberCursors(pParse, aCsrMap, p->pSrc, iExcept);
+ memset(&w, 0, sizeof(w));
+ w.u.aiCol = aCsrMap;
+ w.xExprCallback = renumberCursorsCb;
+ w.xSelectCallback = sqlite3SelectWalkNoop;
+ sqlite3WalkSelect(&w, p);
+}
+#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
+
+#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
+/*
** This routine attempts to flatten subqueries as a performance optimization.
** This routine returns 1 if it makes changes and 0 if no flattening occurs.
**
@@ -133018,9 +135999,9 @@ static void recomputeColumnsUsed(
** (17c) every term within the subquery compound must have a FROM clause
** (17d) the outer query may not be
** (17d1) aggregate, or
-** (17d2) DISTINCT, or
-** (17d3) a join.
-** (17e) the subquery may not contain window functions
+** (17d2) DISTINCT
+** (17e) the subquery may not contain window functions, and
+** (17f) the subquery must not be the RHS of a LEFT JOIN.
**
** The parent and sub-query may contain WHERE clauses. Subject to
** rules (11), (13) and (14), they may also contain ORDER BY,
@@ -133036,8 +136017,8 @@ static void recomputeColumnsUsed(
** syntax error and return a detailed message.
**
** (18) If the sub-query is a compound select, then all terms of the
-** ORDER BY clause of the parent must be simple references to
-** columns of the sub-query.
+** ORDER BY clause of the parent must be copies of a term returned
+** by the parent query.
**
** (19) If the subquery uses LIMIT then the outer query may not
** have a WHERE clause.
@@ -133053,9 +136034,8 @@ static void recomputeColumnsUsed(
**
** (22) The subquery may not be a recursive CTE.
**
-** (**) Subsumed into restriction (17d3). Was: If the outer query is
-** a recursive CTE, then the sub-query may not be a compound query.
-** This restriction is because transforming the
+** (23) If the outer query is a recursive CTE, then the sub-query may not be
+** a compound query. This restriction is because transforming the
** parent to a compound query confuses the code that handles
** recursive queries in multiSelect().
**
@@ -133097,9 +136077,10 @@ static int flattenSubquery(
int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */
int i; /* Loop counter */
Expr *pWhere; /* The WHERE clause */
- struct SrcList_item *pSubitem; /* The subquery */
+ SrcItem *pSubitem; /* The subquery */
sqlite3 *db = pParse->db;
Walker w; /* Walker to persist agginfo data */
+ int *aCsrMap = 0;
/* Check to see if flattening is permitted. Return 0 if not.
*/
@@ -133195,13 +136176,14 @@ static int flattenSubquery(
if( pSub->pOrderBy ){
return 0; /* Restriction (20) */
}
- if( isAgg || (p->selFlags & SF_Distinct)!=0 || pSrc->nSrc!=1 ){
- return 0; /* (17d1), (17d2), or (17d3) */
+ if( isAgg || (p->selFlags & SF_Distinct)!=0 || isLeftJoin>0 ){
+ return 0; /* (17d1), (17d2), or (17f) */
}
for(pSub1=pSub; pSub1; pSub1=pSub1->pPrior){
testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct );
testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Aggregate );
assert( pSub->pSrc!=0 );
+ assert( (pSub->selFlags & SF_Recursive)==0 );
assert( pSub->pEList->nExpr==pSub1->pEList->nExpr );
if( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))!=0 /* (17b) */
|| (pSub1->pPrior && pSub1->op!=TK_ALL) /* (17a) */
@@ -133222,15 +136204,15 @@ static int flattenSubquery(
if( p->pOrderBy->a[ii].u.x.iOrderByCol==0 ) return 0;
}
}
- }
- /* Ex-restriction (23):
- ** The only way that the recursive part of a CTE can contain a compound
- ** subquery is for the subquery to be one term of a join. But if the
- ** subquery is a join, then the flattening has already been stopped by
- ** restriction (17d3)
- */
- assert( (p->selFlags & SF_Recursive)==0 || pSub->pPrior==0 );
+ /* Restriction (23) */
+ if( (p->selFlags & SF_Recursive) ) return 0;
+
+ if( pSrc->nSrc>1 ){
+ if( pParse->nSelect>500 ) return 0;
+ aCsrMap = sqlite3DbMallocZero(db, pParse->nTab*sizeof(int));
+ }
+ }
/***** If we reach this point, flattening is permitted. *****/
SELECTTRACE(1,pParse,p,("flatten %u.%p from term %d\n",
@@ -133242,6 +136224,17 @@ static int flattenSubquery(
testcase( i==SQLITE_DENY );
pParse->zAuthContext = zSavedAuthContext;
+ /* Delete the transient structures associated with thesubquery */
+ pSub1 = pSubitem->pSelect;
+ sqlite3DbFree(db, pSubitem->zDatabase);
+ sqlite3DbFree(db, pSubitem->zName);
+ sqlite3DbFree(db, pSubitem->zAlias);
+ pSubitem->zDatabase = 0;
+ pSubitem->zName = 0;
+ pSubitem->zAlias = 0;
+ pSubitem->pSelect = 0;
+ assert( pSubitem->pOn==0 );
+
/* If the sub-query is a compound SELECT statement, then (by restrictions
** 17 and 18 above) it must be a UNION ALL and the parent query must
** be of the form:
@@ -133280,18 +136273,23 @@ static int flattenSubquery(
ExprList *pOrderBy = p->pOrderBy;
Expr *pLimit = p->pLimit;
Select *pPrior = p->pPrior;
+ Table *pItemTab = pSubitem->pTab;
+ pSubitem->pTab = 0;
p->pOrderBy = 0;
- p->pSrc = 0;
p->pPrior = 0;
p->pLimit = 0;
pNew = sqlite3SelectDup(db, p, 0);
p->pLimit = pLimit;
p->pOrderBy = pOrderBy;
- p->pSrc = pSrc;
p->op = TK_ALL;
+ pSubitem->pTab = pItemTab;
if( pNew==0 ){
p->pPrior = pPrior;
}else{
+ pNew->selId = ++pParse->nSelect;
+ if( aCsrMap && ALWAYS(db->mallocFailed==0) ){
+ renumberCursors(pParse, pNew, iFrom, aCsrMap);
+ }
pNew->pPrior = pPrior;
if( pPrior ) pPrior->pNext = pNew;
pNew->pNext = p;
@@ -133299,24 +136297,13 @@ static int flattenSubquery(
SELECTTRACE(2,pParse,p,("compound-subquery flattener"
" creates %u as peer\n",pNew->selId));
}
- if( db->mallocFailed ) return 1;
+ assert( pSubitem->pSelect==0 );
+ }
+ sqlite3DbFree(db, aCsrMap);
+ if( db->mallocFailed ){
+ pSubitem->pSelect = pSub1;
+ return 1;
}
-
- /* Begin flattening the iFrom-th entry of the FROM clause
- ** in the outer query.
- */
- pSub = pSub1 = pSubitem->pSelect;
-
- /* Delete the transient table structure associated with the
- ** subquery
- */
- sqlite3DbFree(db, pSubitem->zDatabase);
- sqlite3DbFree(db, pSubitem->zName);
- sqlite3DbFree(db, pSubitem->zAlias);
- pSubitem->zDatabase = 0;
- pSubitem->zName = 0;
- pSubitem->zAlias = 0;
- pSubitem->pSelect = 0;
/* Defer deleting the Table object associated with the
** subquery until code generation is
@@ -133329,8 +136316,10 @@ static int flattenSubquery(
Table *pTabToDel = pSubitem->pTab;
if( pTabToDel->nTabRef==1 ){
Parse *pToplevel = sqlite3ParseToplevel(pParse);
- pTabToDel->pNextZombie = pToplevel->pZombieTab;
- pToplevel->pZombieTab = pTabToDel;
+ sqlite3ParserAddCleanup(pToplevel,
+ (void(*)(sqlite3*,void*))sqlite3DeleteTable,
+ pTabToDel);
+ testcase( pToplevel->earlyCleanup );
}else{
pTabToDel->nTabRef--;
}
@@ -133350,6 +136339,7 @@ static int flattenSubquery(
** those references with expressions that resolve to the subquery FROM
** elements we are now copying in.
*/
+ pSub = pSub1;
for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){
int nSubSrc;
u8 jointype = 0;
@@ -133358,14 +136348,8 @@ static int flattenSubquery(
nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */
pSrc = pParent->pSrc; /* FROM clause of the outer query */
- if( pSrc ){
- assert( pParent==p ); /* First time through the loop */
- jointype = pSubitem->fg.jointype;
- }else{
- assert( pParent!=p ); /* 2nd and subsequent times through the loop */
- pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0);
- if( pSrc==0 ) break;
- pParent->pSrc = pSrc;
+ if( pParent==p ){
+ jointype = pSubitem->fg.jointype; /* First time through the loop */
}
/* The subquery uses a single slot of the FROM clause of the outer
@@ -133485,7 +136469,7 @@ static int flattenSubquery(
sqlite3SelectDelete(db, pSub1);
#if SELECTTRACE_ENABLED
- if( sqlite3_unsupported_selecttrace & 0x100 ){
+ if( sqlite3SelectTrace & 0x100 ){
SELECTTRACE(0x100,pParse,p,("After flattening:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -133502,8 +136486,10 @@ static int flattenSubquery(
typedef struct WhereConst WhereConst;
struct WhereConst {
Parse *pParse; /* Parsing context */
+ u8 *pOomFault; /* Pointer to pParse->db->mallocFailed */
int nConst; /* Number for COLUMN=CONSTANT terms */
int nChng; /* Number of times a constant is propagated */
+ int bHasAffBlob; /* At least one column in apExpr[] as affinity BLOB */
Expr **apExpr; /* [i*2] is COLUMN and [i*2+1] is VALUE */
};
@@ -133542,6 +136528,9 @@ static void constInsert(
return; /* Already present. Return without doing anything. */
}
}
+ if( sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){
+ pConst->bHasAffBlob = 1;
+ }
pConst->nConst++;
pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr,
@@ -133562,7 +136551,7 @@ static void constInsert(
*/
static void findConstInWhere(WhereConst *pConst, Expr *pExpr){
Expr *pRight, *pLeft;
- if( pExpr==0 ) return;
+ if( NEVER(pExpr==0) ) return;
if( ExprHasProperty(pExpr, EP_FromJoin) ) return;
if( pExpr->op==TK_AND ){
findConstInWhere(pConst, pExpr->pRight);
@@ -133583,38 +136572,84 @@ static void findConstInWhere(WhereConst *pConst, Expr *pExpr){
}
/*
-** This is a Walker expression callback. pExpr is a candidate expression
-** to be replaced by a value. If pExpr is equivalent to one of the
-** columns named in pWalker->u.pConst, then overwrite it with its
-** corresponding value.
+** This is a helper function for Walker callback propagateConstantExprRewrite().
+**
+** Argument pExpr is a candidate expression to be replaced by a value. If
+** pExpr is equivalent to one of the columns named in pWalker->u.pConst,
+** then overwrite it with the corresponding value. Except, do not do so
+** if argument bIgnoreAffBlob is non-zero and the affinity of pExpr
+** is SQLITE_AFF_BLOB.
*/
-static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){
+static int propagateConstantExprRewriteOne(
+ WhereConst *pConst,
+ Expr *pExpr,
+ int bIgnoreAffBlob
+){
int i;
- WhereConst *pConst;
+ if( pConst->pOomFault[0] ) return WRC_Prune;
if( pExpr->op!=TK_COLUMN ) return WRC_Continue;
if( ExprHasProperty(pExpr, EP_FixedCol|EP_FromJoin) ){
testcase( ExprHasProperty(pExpr, EP_FixedCol) );
testcase( ExprHasProperty(pExpr, EP_FromJoin) );
return WRC_Continue;
}
- pConst = pWalker->u.pConst;
for(i=0; i<pConst->nConst; i++){
Expr *pColumn = pConst->apExpr[i*2];
if( pColumn==pExpr ) continue;
if( pColumn->iTable!=pExpr->iTable ) continue;
if( pColumn->iColumn!=pExpr->iColumn ) continue;
+ if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){
+ break;
+ }
/* A match is found. Add the EP_FixedCol property */
pConst->nChng++;
ExprClearProperty(pExpr, EP_Leaf);
ExprSetProperty(pExpr, EP_FixedCol);
assert( pExpr->pLeft==0 );
pExpr->pLeft = sqlite3ExprDup(pConst->pParse->db, pConst->apExpr[i*2+1], 0);
+ if( pConst->pParse->db->mallocFailed ) return WRC_Prune;
break;
}
return WRC_Prune;
}
/*
+** This is a Walker expression callback. pExpr is a node from the WHERE
+** clause of a SELECT statement. This function examines pExpr to see if
+** any substitutions based on the contents of pWalker->u.pConst should
+** be made to pExpr or its immediate children.
+**
+** A substitution is made if:
+**
+** + pExpr is a column with an affinity other than BLOB that matches
+** one of the columns in pWalker->u.pConst, or
+**
+** + pExpr is a binary comparison operator (=, <=, >=, <, >) that
+** uses an affinity other than TEXT and one of its immediate
+** children is a column that matches one of the columns in
+** pWalker->u.pConst.
+*/
+static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){
+ WhereConst *pConst = pWalker->u.pConst;
+ assert( TK_GT==TK_EQ+1 );
+ assert( TK_LE==TK_EQ+2 );
+ assert( TK_LT==TK_EQ+3 );
+ assert( TK_GE==TK_EQ+4 );
+ if( pConst->bHasAffBlob ){
+ if( (pExpr->op>=TK_EQ && pExpr->op<=TK_GE)
+ || pExpr->op==TK_IS
+ ){
+ propagateConstantExprRewriteOne(pConst, pExpr->pLeft, 0);
+ if( pConst->pOomFault[0] ) return WRC_Prune;
+ if( sqlite3ExprAffinity(pExpr->pLeft)!=SQLITE_AFF_TEXT ){
+ propagateConstantExprRewriteOne(pConst, pExpr->pRight, 0);
+ }
+ }
+ }
+ return propagateConstantExprRewriteOne(pConst, pExpr, pConst->bHasAffBlob);
+}
+
+/*
** The WHERE-clause constant propagation optimization.
**
** If the WHERE clause contains terms of the form COLUMN=CONSTANT or
@@ -133649,6 +136684,21 @@ static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){
** routines know to generate the constant "123" instead of looking up the
** column value. Also, to avoid collation problems, this optimization is
** only attempted if the "a=123" term uses the default BINARY collation.
+**
+** 2021-05-25 forum post 6a06202608: Another troublesome case is...
+**
+** CREATE TABLE t1(x);
+** INSERT INTO t1 VALUES(10.0);
+** SELECT 1 FROM t1 WHERE x=10 AND x LIKE 10;
+**
+** The query should return no rows, because the t1.x value is '10.0' not '10'
+** and '10.0' is not LIKE '10'. But if we are not careful, the first WHERE
+** term "x=10" will cause the second WHERE term to become "10 LIKE 10",
+** resulting in a false positive. To avoid this, constant propagation for
+** columns with BLOB affinity is only allowed if the constant is used with
+** operators ==, <=, <, >=, >, or IS in a way that will cause the correct
+** type conversions to occur. See logic associated with the bHasAffBlob flag
+** for details.
*/
static int propagateConstants(
Parse *pParse, /* The parsing context */
@@ -133658,10 +136708,12 @@ static int propagateConstants(
Walker w;
int nChng = 0;
x.pParse = pParse;
+ x.pOomFault = &pParse->db->mallocFailed;
do{
x.nConst = 0;
x.nChng = 0;
x.apExpr = 0;
+ x.bHasAffBlob = 0;
findConstInWhere(&x, p->pWhere);
if( x.nConst ){
memset(&w, 0, sizeof(w));
@@ -133680,6 +136732,35 @@ static int propagateConstants(
}
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
+# if !defined(SQLITE_OMIT_WINDOWFUNC)
+/*
+** This function is called to determine whether or not it is safe to
+** push WHERE clause expression pExpr down to FROM clause sub-query
+** pSubq, which contains at least one window function. Return 1
+** if it is safe and the expression should be pushed down, or 0
+** otherwise.
+**
+** It is only safe to push the expression down if it consists only
+** of constants and copies of expressions that appear in the PARTITION
+** BY clause of all window function used by the sub-query. It is safe
+** to filter out entire partitions, but not rows within partitions, as
+** this may change the results of the window functions.
+**
+** At the time this function is called it is guaranteed that
+**
+** * the sub-query uses only one distinct window frame, and
+** * that the window frame has a PARTITION BY clase.
+*/
+static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){
+ assert( pSubq->pWin->pPartition );
+ assert( (pSubq->selFlags & SF_MultiPart)==0 );
+ assert( pSubq->pPrior==0 );
+ return sqlite3ExprIsConstantOrGroupBy(pParse, pExpr, pSubq->pWin->pPartition);
+}
+# endif /* SQLITE_OMIT_WINDOWFUNC */
+#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
+
+#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
/*
** Make copies of relevant WHERE clause terms of the outer query into
** the WHERE clause of subquery. Example:
@@ -133726,9 +136807,24 @@ static int propagateConstants(
** But if the (b2=2) term were to be pushed down into the bb subquery,
** then the (1,1,NULL) row would be suppressed.
**
-** (6) The inner query features one or more window-functions (since
-** changes to the WHERE clause of the inner query could change the
-** window over which window functions are calculated).
+** (6) Window functions make things tricky as changes to the WHERE clause
+** of the inner query could change the window over which window
+** functions are calculated. Therefore, do not attempt the optimization
+** if:
+**
+** (6a) The inner query uses multiple incompatible window partitions.
+**
+** (6b) The inner query is a compound and uses window-functions.
+**
+** (6c) The WHERE clause does not consist entirely of constants and
+** copies of expressions found in the PARTITION BY clause of
+** all window-functions used by the sub-query. It is safe to
+** filter out entire partitions, as this does not change the
+** window over which any window-function is calculated.
+**
+** (7) The inner query is a Common Table Expression (CTE) that should
+** be materialized. (This restriction is implemented in the calling
+** routine.)
**
** Return 0 if no changes are made and non-zero if one or more WHERE clause
** terms are duplicated into the subquery.
@@ -133742,13 +136838,17 @@ static int pushDownWhereTerms(
){
Expr *pNew;
int nChng = 0;
- Select *pSel;
if( pWhere==0 ) return 0;
- if( pSubq->selFlags & SF_Recursive ) return 0; /* restriction (2) */
+ if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ) return 0;
#ifndef SQLITE_OMIT_WINDOWFUNC
- for(pSel=pSubq; pSel; pSel=pSel->pPrior){
- if( pSel->pWin ) return 0; /* restriction (6) */
+ if( pSubq->pPrior ){
+ Select *pSel;
+ for(pSel=pSubq; pSel; pSel=pSel->pPrior){
+ if( pSel->pWin ) return 0; /* restriction (6b) */
+ }
+ }else{
+ if( pSubq->pWin && pSubq->pWin->pPartition==0 ) return 0;
}
#endif
@@ -133784,6 +136884,7 @@ static int pushDownWhereTerms(
}
if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){
nChng++;
+ pSubq->selFlags |= SF_PushDown;
while( pSubq ){
SubstContext x;
pNew = sqlite3ExprDup(pParse->db, pWhere, 0);
@@ -133794,6 +136895,14 @@ static int pushDownWhereTerms(
x.isLeftJoin = 0;
x.pEList = pSubq->pEList;
pNew = substExpr(&x, pNew);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( pSubq->pWin && 0==pushDownWindowCheck(pParse, pSubq, pNew) ){
+ /* Restriction 6c has prevented push-down in this case */
+ sqlite3ExprDelete(pParse->db, pNew);
+ nChng--;
+ break;
+ }
+#endif
if( pSubq->selFlags & SF_Aggregate ){
pSubq->pHaving = sqlite3ExprAnd(pParse, pSubq->pHaving, pNew);
}else{
@@ -133832,7 +136941,11 @@ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){
assert( *ppMinMax==0 );
assert( pFunc->op==TK_AGG_FUNCTION );
assert( !IsWindowFunc(pFunc) );
- if( pEList==0 || pEList->nExpr!=1 || ExprHasProperty(pFunc, EP_WinFunc) ){
+ if( pEList==0
+ || pEList->nExpr!=1
+ || ExprHasProperty(pFunc, EP_WinFunc)
+ || OptimizationDisabled(db, SQLITE_MinMaxOpt)
+ ){
return eRet;
}
zFunc = pFunc->u.zToken;
@@ -133895,24 +137008,26 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){
** SQLITE_ERROR and leave an error in pParse. Otherwise, populate
** pFrom->pIndex and return SQLITE_OK.
*/
-SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *pParse, struct SrcList_item *pFrom){
- if( pFrom->pTab && pFrom->fg.isIndexedBy ){
- Table *pTab = pFrom->pTab;
- char *zIndexedBy = pFrom->u1.zIndexedBy;
- Index *pIdx;
- for(pIdx=pTab->pIndex;
- pIdx && sqlite3StrICmp(pIdx->zName, zIndexedBy);
- pIdx=pIdx->pNext
- );
- if( !pIdx ){
- sqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy, 0);
- pParse->checkSchema = 1;
- return SQLITE_ERROR;
- }
- pFrom->pIBIndex = pIdx;
+SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){
+ Table *pTab = pFrom->pTab;
+ char *zIndexedBy = pFrom->u1.zIndexedBy;
+ Index *pIdx;
+ assert( pTab!=0 );
+ assert( pFrom->fg.isIndexedBy!=0 );
+
+ for(pIdx=pTab->pIndex;
+ pIdx && sqlite3StrICmp(pIdx->zName, zIndexedBy);
+ pIdx=pIdx->pNext
+ );
+ if( !pIdx ){
+ sqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy, 0);
+ pParse->checkSchema = 1;
+ return SQLITE_ERROR;
}
+ pFrom->u2.pIBIndex = pIdx;
return SQLITE_OK;
}
+
/*
** Detect compound SELECT statements that use an ORDER BY clause with
** an alternative collating sequence.
@@ -133999,7 +137114,7 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){
** arguments. If it does, leave an error message in pParse and return
** non-zero, since pFrom is not allowed to be a table-valued function.
*/
-static int cannotBeFunction(Parse *pParse, struct SrcList_item *pFrom){
+static int cannotBeFunction(Parse *pParse, SrcItem *pFrom){
if( pFrom->fg.isTabFunc ){
sqlite3ErrorMsg(pParse, "'%s' is not a function", pFrom->zName);
return 1;
@@ -134020,21 +137135,22 @@ static int cannotBeFunction(Parse *pParse, struct SrcList_item *pFrom){
*/
static struct Cte *searchWith(
With *pWith, /* Current innermost WITH clause */
- struct SrcList_item *pItem, /* FROM clause element to resolve */
+ SrcItem *pItem, /* FROM clause element to resolve */
With **ppContext /* OUT: WITH clause return value belongs to */
){
- const char *zName;
- if( pItem->zDatabase==0 && (zName = pItem->zName)!=0 ){
- With *p;
- for(p=pWith; p; p=p->pOuter){
- int i;
- for(i=0; i<p->nCte; i++){
- if( sqlite3StrICmp(zName, p->a[i].zName)==0 ){
- *ppContext = p;
- return &p->a[i];
- }
+ const char *zName = pItem->zName;
+ With *p;
+ assert( pItem->zDatabase==0 );
+ assert( zName!=0 );
+ for(p=pWith; p; p=p->pOuter){
+ int i;
+ for(i=0; i<p->nCte; i++){
+ if( sqlite3StrICmp(zName, p->a[i].zName)==0 ){
+ *ppContext = p;
+ return &p->a[i];
}
}
+ if( p->bView ) break;
}
return 0;
}
@@ -134044,58 +137160,92 @@ static struct Cte *searchWith(
**
** This routine pushes the WITH clause passed as the second argument
** onto the top of the stack. If argument bFree is true, then this
-** WITH clause will never be popped from the stack. In this case it
-** should be freed along with the Parse object. In other cases, when
+** WITH clause will never be popped from the stack but should instead
+** be freed along with the Parse object. In other cases, when
** bFree==0, the With object will be freed along with the SELECT
** statement with which it is associated.
+**
+** This routine returns a copy of pWith. Or, if bFree is true and
+** the pWith object is destroyed immediately due to an OOM condition,
+** then this routine return NULL.
+**
+** If bFree is true, do not continue to use the pWith pointer after
+** calling this routine, Instead, use only the return value.
*/
-SQLITE_PRIVATE void sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){
- assert( bFree==0 || (pParse->pWith==0 && pParse->pWithToFree==0) );
+SQLITE_PRIVATE With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){
if( pWith ){
- assert( pParse->pWith!=pWith );
- pWith->pOuter = pParse->pWith;
- pParse->pWith = pWith;
- if( bFree ) pParse->pWithToFree = pWith;
+ if( bFree ){
+ pWith = (With*)sqlite3ParserAddCleanup(pParse,
+ (void(*)(sqlite3*,void*))sqlite3WithDelete,
+ pWith);
+ if( pWith==0 ) return 0;
+ }
+ if( pParse->nErr==0 ){
+ assert( pParse->pWith!=pWith );
+ pWith->pOuter = pParse->pWith;
+ pParse->pWith = pWith;
+ }
}
+ return pWith;
}
/*
** This function checks if argument pFrom refers to a CTE declared by
-** a WITH clause on the stack currently maintained by the parser. And,
-** if currently processing a CTE expression, if it is a recursive
-** reference to the current CTE.
+** a WITH clause on the stack currently maintained by the parser (on the
+** pParse->pWith linked list). And if currently processing a CTE
+** CTE expression, through routine checks to see if the reference is
+** a recursive reference to the CTE.
**
-** If pFrom falls into either of the two categories above, pFrom->pTab
-** and other fields are populated accordingly. The caller should check
-** (pFrom->pTab!=0) to determine whether or not a successful match
-** was found.
+** If pFrom matches a CTE according to either of these two above, pFrom->pTab
+** and other fields are populated accordingly.
**
-** Whether or not a match is found, SQLITE_OK is returned if no error
-** occurs. If an error does occur, an error message is stored in the
-** parser and some error code other than SQLITE_OK returned.
+** Return 0 if no match is found.
+** Return 1 if a match is found.
+** Return 2 if an error condition is detected.
*/
-static int withExpand(
- Walker *pWalker,
- struct SrcList_item *pFrom
+static int resolveFromTermToCte(
+ Parse *pParse, /* The parsing context */
+ Walker *pWalker, /* Current tree walker */
+ SrcItem *pFrom /* The FROM clause term to check */
){
- Parse *pParse = pWalker->pParse;
- sqlite3 *db = pParse->db;
- struct Cte *pCte; /* Matched CTE (or NULL if no match) */
- With *pWith; /* WITH clause that pCte belongs to */
+ Cte *pCte; /* Matched CTE (or NULL if no match) */
+ With *pWith; /* The matching WITH */
assert( pFrom->pTab==0 );
+ if( pParse->pWith==0 ){
+ /* There are no WITH clauses in the stack. No match is possible */
+ return 0;
+ }
if( pParse->nErr ){
- return SQLITE_ERROR;
+ /* Prior errors might have left pParse->pWith in a goofy state, so
+ ** go no further. */
+ return 0;
+ }
+ if( pFrom->zDatabase!=0 ){
+ /* The FROM term contains a schema qualifier (ex: main.t1) and so
+ ** it cannot possibly be a CTE reference. */
+ return 0;
+ }
+ if( pFrom->fg.notCte ){
+ /* The FROM term is specifically excluded from matching a CTE.
+ ** (1) It is part of a trigger that used to have zDatabase but had
+ ** zDatabase removed by sqlite3FixTriggerStep().
+ ** (2) This is the first term in the FROM clause of an UPDATE.
+ */
+ return 0;
}
-
pCte = searchWith(pParse->pWith, pFrom, &pWith);
if( pCte ){
+ sqlite3 *db = pParse->db;
Table *pTab;
ExprList *pEList;
Select *pSel;
Select *pLeft; /* Left-most SELECT statement */
+ Select *pRecTerm; /* Left-most recursive term */
int bMayRecursive; /* True if compound joined by UNION [ALL] */
With *pSavedWith; /* Initial value of pParse->pWith */
+ int iRecTab = -1; /* Cursor for recursive table */
+ CteUse *pCteUse;
/* If pCte->zCteErr is non-NULL at this point, then this is an illegal
** recursive reference to CTE pCte. Leave an error in pParse and return
@@ -134103,63 +137253,94 @@ static int withExpand(
** In this case, proceed. */
if( pCte->zCteErr ){
sqlite3ErrorMsg(pParse, pCte->zCteErr, pCte->zName);
- return SQLITE_ERROR;
+ return 2;
}
- if( cannotBeFunction(pParse, pFrom) ) return SQLITE_ERROR;
+ if( cannotBeFunction(pParse, pFrom) ) return 2;
assert( pFrom->pTab==0 );
- pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table));
- if( pTab==0 ) return WRC_Abort;
+ pTab = sqlite3DbMallocZero(db, sizeof(Table));
+ if( pTab==0 ) return 2;
+ pCteUse = pCte->pUse;
+ if( pCteUse==0 ){
+ pCte->pUse = pCteUse = sqlite3DbMallocZero(db, sizeof(pCteUse[0]));
+ if( pCteUse==0
+ || sqlite3ParserAddCleanup(pParse,sqlite3DbFree,pCteUse)==0
+ ){
+ sqlite3DbFree(db, pTab);
+ return 2;
+ }
+ pCteUse->eM10d = pCte->eM10d;
+ }
+ pFrom->pTab = pTab;
pTab->nTabRef = 1;
pTab->zName = sqlite3DbStrDup(db, pCte->zName);
pTab->iPKey = -1;
pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) );
pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid;
pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0);
- if( db->mallocFailed ) return SQLITE_NOMEM_BKPT;
+ if( db->mallocFailed ) return 2;
+ pFrom->pSelect->selFlags |= SF_CopyCte;
assert( pFrom->pSelect );
+ pFrom->fg.isCte = 1;
+ pFrom->u2.pCteUse = pCteUse;
+ pCteUse->nUse++;
+ if( pCteUse->nUse>=2 && pCteUse->eM10d==M10d_Any ){
+ pCteUse->eM10d = M10d_Yes;
+ }
/* Check if this is a recursive CTE. */
- pSel = pFrom->pSelect;
+ pRecTerm = pSel = pFrom->pSelect;
bMayRecursive = ( pSel->op==TK_ALL || pSel->op==TK_UNION );
- if( bMayRecursive ){
+ while( bMayRecursive && pRecTerm->op==pSel->op ){
int i;
- SrcList *pSrc = pFrom->pSelect->pSrc;
+ SrcList *pSrc = pRecTerm->pSrc;
+ assert( pRecTerm->pPrior!=0 );
for(i=0; i<pSrc->nSrc; i++){
- struct SrcList_item *pItem = &pSrc->a[i];
+ SrcItem *pItem = &pSrc->a[i];
if( pItem->zDatabase==0
&& pItem->zName!=0
&& 0==sqlite3StrICmp(pItem->zName, pCte->zName)
- ){
+ ){
pItem->pTab = pTab;
- pItem->fg.isRecursive = 1;
pTab->nTabRef++;
- pSel->selFlags |= SF_Recursive;
+ pItem->fg.isRecursive = 1;
+ if( pRecTerm->selFlags & SF_Recursive ){
+ sqlite3ErrorMsg(pParse,
+ "multiple references to recursive table: %s", pCte->zName
+ );
+ return 2;
+ }
+ pRecTerm->selFlags |= SF_Recursive;
+ if( iRecTab<0 ) iRecTab = pParse->nTab++;
+ pItem->iCursor = iRecTab;
}
}
+ if( (pRecTerm->selFlags & SF_Recursive)==0 ) break;
+ pRecTerm = pRecTerm->pPrior;
}
- /* Only one recursive reference is permitted. */
- if( pTab->nTabRef>2 ){
- sqlite3ErrorMsg(
- pParse, "multiple references to recursive table: %s", pCte->zName
- );
- return SQLITE_ERROR;
- }
- assert( pTab->nTabRef==1 ||
- ((pSel->selFlags&SF_Recursive) && pTab->nTabRef==2 ));
-
pCte->zCteErr = "circular reference: %s";
pSavedWith = pParse->pWith;
pParse->pWith = pWith;
- if( bMayRecursive ){
- Select *pPrior = pSel->pPrior;
- assert( pPrior->pWith==0 );
- pPrior->pWith = pSel->pWith;
- sqlite3WalkSelect(pWalker, pPrior);
- pPrior->pWith = 0;
+ if( pSel->selFlags & SF_Recursive ){
+ int rc;
+ assert( pRecTerm!=0 );
+ assert( (pRecTerm->selFlags & SF_Recursive)==0 );
+ assert( pRecTerm->pNext!=0 );
+ assert( (pRecTerm->pNext->selFlags & SF_Recursive)!=0 );
+ assert( pRecTerm->pWith==0 );
+ pRecTerm->pWith = pSel->pWith;
+ rc = sqlite3WalkSelect(pWalker, pRecTerm);
+ pRecTerm->pWith = 0;
+ if( rc ){
+ pParse->pWith = pSavedWith;
+ return 2;
+ }
}else{
- sqlite3WalkSelect(pWalker, pSel);
+ if( sqlite3WalkSelect(pWalker, pSel) ){
+ pParse->pWith = pSavedWith;
+ return 2;
+ }
}
pParse->pWith = pWith;
@@ -134171,7 +137352,7 @@ static int withExpand(
pCte->zName, pEList->nExpr, pCte->pCols->nExpr
);
pParse->pWith = pSavedWith;
- return SQLITE_ERROR;
+ return 2;
}
pEList = pCte->pCols;
}
@@ -134187,9 +137368,9 @@ static int withExpand(
}
pCte->zCteErr = 0;
pParse->pWith = pSavedWith;
+ return 1; /* Success */
}
-
- return SQLITE_OK;
+ return 0; /* No match */
}
#endif
@@ -134202,7 +137383,7 @@ static int withExpand(
** sqlite3SelectExpand() when walking a SELECT tree to resolve table
** names and other FROM clause elements.
*/
-static void selectPopWith(Walker *pWalker, Select *p){
+SQLITE_PRIVATE void sqlite3SelectPopWith(Walker *pWalker, Select *p){
Parse *pParse = pWalker->pParse;
if( OK_IF_ALWAYS_TRUE(pParse->pWith) && p->pPrior==0 ){
With *pWith = findRightmost(p)->pWith;
@@ -134212,8 +137393,6 @@ static void selectPopWith(Walker *pWalker, Select *p){
}
}
}
-#else
-#define selectPopWith 0
#endif
/*
@@ -134223,7 +137402,7 @@ static void selectPopWith(Walker *pWalker, Select *p){
** SQLITE_OK is returned. Otherwise, if an OOM error is encountered,
** SQLITE_NOMEM.
*/
-SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, struct SrcList_item *pFrom){
+SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){
Select *pSel = pFrom->pSelect;
Table *pTab;
@@ -134240,7 +137419,13 @@ SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, struct SrcList_item *pFr
sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol);
pTab->iPKey = -1;
pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) );
- pTab->tabFlags |= TF_Ephemeral;
+#ifndef SQLITE_ALLOW_ROWID_IN_VIEW
+ /* The usual case - do not allow ROWID on a subquery */
+ pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid;
+#else
+ pTab->tabFlags |= TF_Ephemeral; /* Legacy compatibility mode */
+#endif
+
return pParse->nErr ? SQLITE_ERROR : SQLITE_OK;
}
@@ -134271,10 +137456,10 @@ SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, struct SrcList_item *pFr
*/
static int selectExpander(Walker *pWalker, Select *p){
Parse *pParse = pWalker->pParse;
- int i, j, k;
+ int i, j, k, rc;
SrcList *pTabList;
ExprList *pEList;
- struct SrcList_item *pFrom;
+ SrcItem *pFrom;
sqlite3 *db = pParse->db;
Expr *pE, *pRight, *pExpr;
u16 selFlags = p->selFlags;
@@ -134294,6 +137479,15 @@ static int selectExpander(Walker *pWalker, Select *p){
}
pTabList = p->pSrc;
pEList = p->pEList;
+ if( pParse->pWith && (p->selFlags & SF_View) ){
+ if( p->pWith==0 ){
+ p->pWith = (With*)sqlite3DbMallocZero(db, sizeof(With));
+ if( p->pWith==0 ){
+ return WRC_Abort;
+ }
+ }
+ p->pWith->bView = 1;
+ }
sqlite3WithPush(pParse, p->pWith, 0);
/* Make sure cursor numbers have been assigned to all entries in
@@ -134310,10 +137504,6 @@ static int selectExpander(Walker *pWalker, Select *p){
assert( pFrom->fg.isRecursive==0 || pFrom->pTab!=0 );
if( pFrom->pTab ) continue;
assert( pFrom->fg.isRecursive==0 );
-#ifndef SQLITE_OMIT_CTE
- if( withExpand(pWalker, pFrom) ) return WRC_Abort;
- if( pFrom->pTab ) {} else
-#endif
if( pFrom->zName==0 ){
#ifndef SQLITE_OMIT_SUBQUERY
Select *pSel = pFrom->pSelect;
@@ -134323,6 +137513,12 @@ static int selectExpander(Walker *pWalker, Select *p){
if( sqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort;
if( sqlite3ExpandSubquery(pParse, pFrom) ) return WRC_Abort;
#endif
+#ifndef SQLITE_OMIT_CTE
+ }else if( (rc = resolveFromTermToCte(pParse, pWalker, pFrom))!=0 ){
+ if( rc>1 ) return WRC_Abort;
+ pTab = pFrom->pTab;
+ assert( pTab!=0 );
+#endif
}else{
/* An ordinary table or view name in the FROM clause */
assert( pFrom->pTab==0 );
@@ -134344,11 +137540,15 @@ static int selectExpander(Walker *pWalker, Select *p){
u8 eCodeOrig = pWalker->eCode;
if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort;
assert( pFrom->pSelect==0 );
- if( pTab->pSelect && (db->flags & SQLITE_EnableView)==0 ){
+ if( pTab->pSelect
+ && (db->flags & SQLITE_EnableView)==0
+ && pTab->pSchema!=db->aDb[1].pSchema
+ ){
sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited",
pTab->zName);
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
+ assert( SQLITE_VTABRISK_Normal==1 && SQLITE_VTABRISK_High==2 );
if( IsVirtual(pTab)
&& pFrom->fg.fromDDL
&& ALWAYS(pTab->pVTable!=0)
@@ -134370,7 +137570,7 @@ static int selectExpander(Walker *pWalker, Select *p){
}
/* Locate the index named by the INDEXED BY clause, if any. */
- if( sqlite3IndexedByLookup(pParse, pFrom) ){
+ if( pFrom->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pFrom) ){
return WRC_Abort;
}
}
@@ -134589,7 +137789,7 @@ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){
sqlite3WalkSelect(&w, pSelect);
}
w.xSelectCallback = selectExpander;
- w.xSelectCallback2 = selectPopWith;
+ w.xSelectCallback2 = sqlite3SelectPopWith;
w.eCode = 0;
sqlite3WalkSelect(&w, pSelect);
}
@@ -134613,7 +137813,7 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){
Parse *pParse;
int i;
SrcList *pTabList;
- struct SrcList_item *pFrom;
+ SrcItem *pFrom;
assert( p->selFlags & SF_Resolved );
if( p->selFlags & SF_HasTypeInfo ) return;
@@ -134722,8 +137922,10 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){
pFunc->iDistinct = -1;
}else{
KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pE->x.pList,0,0);
- sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0,
- (char*)pKeyInfo, P4_KEYINFO);
+ pFunc->iDistAddr = sqlite3VdbeAddOp4(v, OP_OpenEphemeral,
+ pFunc->iDistinct, 0, 0, (char*)pKeyInfo, P4_KEYINFO);
+ ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s(DISTINCT)",
+ pFunc->pFunc->zName));
}
}
}
@@ -134755,7 +137957,12 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){
** registers if register regAcc contains 0. The caller will take care
** of setting and clearing regAcc.
*/
-static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){
+static void updateAccumulator(
+ Parse *pParse,
+ int regAcc,
+ AggInfo *pAggInfo,
+ int eDistinctType
+){
Vdbe *v = pParse->pVdbe;
int i;
int regHit = 0;
@@ -134801,13 +138008,12 @@ static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){
nArg = 0;
regAgg = 0;
}
- if( pF->iDistinct>=0 ){
+ if( pF->iDistinct>=0 && pList ){
if( addrNext==0 ){
addrNext = sqlite3VdbeMakeLabel(pParse);
}
- testcase( nArg==0 ); /* Error condition */
- testcase( nArg>1 ); /* Also an error */
- codeDistinct(pParse, pF->iDistinct, addrNext, 1, regAgg);
+ pF->iDistinct = codeDistinct(pParse, eDistinctType,
+ pF->iDistinct, addrNext, pList, regAgg);
}
if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){
CollSeq *pColl = 0;
@@ -134859,7 +138065,7 @@ static void explainSimpleCount(
){
if( pParse->explain==2 ){
int bCover = (pIdx!=0 && (HasRowid(pTab) || !IsPrimaryKeyIndex(pIdx)));
- sqlite3VdbeExplain(pParse, 0, "SCAN TABLE %s%s%s",
+ sqlite3VdbeExplain(pParse, 0, "SCAN %s%s%s",
pTab->zName,
bCover ? " USING COVERING INDEX " : "",
bCover ? pIdx->zName : ""
@@ -134884,7 +138090,9 @@ static void explainSimpleCount(
static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){
if( pExpr->op!=TK_AND ){
Select *pS = pWalker->u.pSelect;
- if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy) ){
+ if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy)
+ && ExprAlwaysFalse(pExpr)==0
+ ){
sqlite3 *db = pWalker->pParse->db;
Expr *pNew = sqlite3Expr(db, TK_INTEGER, "1");
if( pNew ){
@@ -134923,7 +138131,7 @@ static void havingToWhere(Parse *pParse, Select *p){
sWalker.u.pSelect = p;
sqlite3WalkExpr(&sWalker, p->pHaving);
#if SELECTTRACE_ENABLED
- if( sWalker.eCode && (sqlite3_unsupported_selecttrace & 0x100)!=0 ){
+ if( sWalker.eCode && (sqlite3SelectTrace & 0x100)!=0 ){
SELECTTRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -134935,11 +138143,13 @@ static void havingToWhere(Parse *pParse, Select *p){
** If it is, then return the SrcList_item for the prior view. If it is not,
** then return 0.
*/
-static struct SrcList_item *isSelfJoinView(
+static SrcItem *isSelfJoinView(
SrcList *pTabList, /* Search for self-joins in this FROM clause */
- struct SrcList_item *pThis /* Search for prior reference to this subquery */
+ SrcItem *pThis /* Search for prior reference to this subquery */
){
- struct SrcList_item *pItem;
+ SrcItem *pItem;
+ assert( pThis->pSelect!=0 );
+ if( pThis->pSelect->selFlags & SF_PushDown ) return 0;
for(pItem = pTabList->a; pItem<pThis; pItem++){
Select *pS1;
if( pItem->pSelect==0 ) continue;
@@ -134955,9 +138165,7 @@ static struct SrcList_item *isSelfJoinView(
** names in the same FROM clause. */
continue;
}
- if( sqlite3ExprCompare(0, pThis->pSelect->pWhere, pS1->pWhere, -1)
- || sqlite3ExprCompare(0, pThis->pSelect->pHaving, pS1->pHaving, -1)
- ){
+ if( pItem->pSelect->selFlags & SF_PushDown ){
/* The view was modified by some other optimization such as
** pushDownWhereTerms() */
continue;
@@ -134967,6 +138175,15 @@ static struct SrcList_item *isSelfJoinView(
return 0;
}
+/*
+** Deallocate a single AggInfo object
+*/
+static void agginfoFree(sqlite3 *db, AggInfo *p){
+ sqlite3DbFree(db, p->aCol);
+ sqlite3DbFree(db, p->aFunc);
+ sqlite3DbFreeNN(db, p);
+}
+
#ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION
/*
** Attempt to transform a query of the form
@@ -135045,7 +138262,7 @@ static int countOfViewOptimization(Parse *pParse, Select *p){
p->selFlags &= ~SF_Aggregate;
#if SELECTTRACE_ENABLED
- if( sqlite3_unsupported_selecttrace & 0x400 ){
+ if( sqlite3SelectTrace & 0x400 ){
SELECTTRACE(0x400,pParse,p,("After count-of-view optimization:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -135098,7 +138315,7 @@ SQLITE_PRIVATE int sqlite3Select(
if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1;
#if SELECTTRACE_ENABLED
SELECTTRACE(1,pParse,p, ("begin processing:\n", pParse->addrExplain));
- if( sqlite3_unsupported_selecttrace & 0x100 ){
+ if( sqlite3SelectTrace & 0x100 ){
sqlite3TreeViewSelect(0, p, 0);
}
#endif
@@ -135107,15 +138324,24 @@ SQLITE_PRIVATE int sqlite3Select(
assert( p->pOrderBy==0 || pDest->eDest!=SRT_Fifo );
assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistQueue );
assert( p->pOrderBy==0 || pDest->eDest!=SRT_Queue );
- if( IgnorableOrderby(pDest) ){
- assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union ||
- pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard ||
- pDest->eDest==SRT_Queue || pDest->eDest==SRT_DistFifo ||
- pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_Fifo);
- /* If ORDER BY makes no difference in the output then neither does
- ** DISTINCT so it can be removed too. */
- sqlite3ExprListDelete(db, p->pOrderBy);
- p->pOrderBy = 0;
+ if( IgnorableDistinct(pDest) ){
+ assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union ||
+ pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard ||
+ pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo );
+ /* All of these destinations are also able to ignore the ORDER BY clause */
+ if( p->pOrderBy ){
+#if SELECTTRACE_ENABLED
+ SELECTTRACE(1,pParse,p, ("dropping superfluous ORDER BY:\n"));
+ if( sqlite3SelectTrace & 0x100 ){
+ sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY");
+ }
+#endif
+ sqlite3ParserAddCleanup(pParse,
+ (void(*)(sqlite3*,void*))sqlite3ExprListDelete,
+ p->pOrderBy);
+ testcase( pParse->earlyCleanup );
+ p->pOrderBy = 0;
+ }
p->selFlags &= ~SF_Distinct;
p->selFlags |= SF_NoopOrderBy;
}
@@ -135125,7 +138351,7 @@ SQLITE_PRIVATE int sqlite3Select(
}
assert( p->pEList!=0 );
#if SELECTTRACE_ENABLED
- if( sqlite3_unsupported_selecttrace & 0x104 ){
+ if( sqlite3SelectTrace & 0x104 ){
SELECTTRACE(0x104,pParse,p, ("after name resolution:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -135136,9 +138362,9 @@ SQLITE_PRIVATE int sqlite3Select(
** In this case, it is an error if the target object (pSrc->a[0]) name
** or alias is duplicated within FROM clause (pSrc->a[1..n]). */
if( p->selFlags & SF_UpdateFrom ){
- struct SrcList_item *p0 = &p->pSrc->a[0];
+ SrcItem *p0 = &p->pSrc->a[0];
for(i=1; i<p->pSrc->nSrc; i++){
- struct SrcList_item *p1 = &p->pSrc->a[i];
+ SrcItem *p1 = &p->pSrc->a[i];
if( p0->pTab==p1->pTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){
sqlite3ErrorMsg(pParse,
"target object/alias may not appear in FROM clause: %s",
@@ -135150,17 +138376,16 @@ SQLITE_PRIVATE int sqlite3Select(
}
if( pDest->eDest==SRT_Output ){
- generateColumnNames(pParse, p);
+ sqlite3GenerateColumnNames(pParse, p);
}
#ifndef SQLITE_OMIT_WINDOWFUNC
- rc = sqlite3WindowRewrite(pParse, p);
- if( rc ){
+ if( sqlite3WindowRewrite(pParse, p) ){
assert( db->mallocFailed || pParse->nErr>0 );
goto select_end;
}
#if SELECTTRACE_ENABLED
- if( p->pWin && (sqlite3_unsupported_selecttrace & 0x108)!=0 ){
+ if( p->pWin && (sqlite3SelectTrace & 0x108)!=0 ){
SELECTTRACE(0x104,pParse,p, ("after window rewrite:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -135176,7 +138401,7 @@ SQLITE_PRIVATE int sqlite3Select(
*/
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
for(i=0; !p->pPrior && i<pTabList->nSrc; i++){
- struct SrcList_item *pItem = &pTabList->a[i];
+ SrcItem *pItem = &pTabList->a[i];
Select *pSub = pItem->pSelect;
Table *pTab = pItem->pTab;
@@ -135267,7 +138492,7 @@ SQLITE_PRIVATE int sqlite3Select(
rc = multiSelect(pParse, p, pDest);
#if SELECTTRACE_ENABLED
SELECTTRACE(0x1,pParse,p,("end compound-select processing\n"));
- if( (sqlite3_unsupported_selecttrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
+ if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
sqlite3TreeViewSelect(0, p, 0);
}
#endif
@@ -135281,12 +138506,13 @@ SQLITE_PRIVATE int sqlite3Select(
** as the equivalent optimization will be handled by query planner in
** sqlite3WhereBegin().
*/
- if( pTabList->nSrc>1
+ if( p->pWhere!=0
+ && p->pWhere->op==TK_AND
&& OptimizationEnabled(db, SQLITE_PropagateConst)
&& propagateConstants(pParse, p)
){
#if SELECTTRACE_ENABLED
- if( sqlite3_unsupported_selecttrace & 0x100 ){
+ if( sqlite3SelectTrace & 0x100 ){
SELECTTRACE(0x100,pParse,p,("After constant propagation:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -135310,7 +138536,8 @@ SQLITE_PRIVATE int sqlite3Select(
** (2) Generate code for all sub-queries
*/
for(i=0; i<pTabList->nSrc; i++){
- struct SrcList_item *pItem = &pTabList->a[i];
+ SrcItem *pItem = &pTabList->a[i];
+ SrcItem *pPrior;
SelectDest dest;
Select *pSub;
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
@@ -135343,19 +138570,8 @@ SQLITE_PRIVATE int sqlite3Select(
pSub = pItem->pSelect;
if( pSub==0 ) continue;
- /* The code for a subquery should only be generated once, though it is
- ** technically harmless for it to be generated multiple times. The
- ** following assert() will detect if something changes to cause
- ** the same subquery to be coded multiple times, as a signal to the
- ** developers to try to optimize the situation.
- **
- ** Update 2019-07-24:
- ** See ticket https://sqlite.org/src/tktview/c52b09c7f38903b1311cec40.
- ** The dbsqlfuzz fuzzer found a case where the same subquery gets
- ** coded twice. So this assert() now becomes a testcase(). It should
- ** be very rare, though.
- */
- testcase( pItem->addrFillSub!=0 );
+ /* The code for a subquery should only be generated once. */
+ assert( pItem->addrFillSub==0 );
/* Increment Parse.nHeight by the height of the largest expression
** tree referred to by this, the parent select. The child select
@@ -135370,16 +138586,18 @@ SQLITE_PRIVATE int sqlite3Select(
** inside the subquery. This can help the subquery to run more efficiently.
*/
if( OptimizationEnabled(db, SQLITE_PushDown)
+ && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes)
&& pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor,
(pItem->fg.jointype & JT_OUTER)!=0)
){
#if SELECTTRACE_ENABLED
- if( sqlite3_unsupported_selecttrace & 0x100 ){
+ if( sqlite3SelectTrace & 0x100 ){
SELECTTRACE(0x100,pParse,p,
("After WHERE-clause push-down into subquery %d:\n", pSub->selId));
sqlite3TreeViewSelect(0, p, 0);
}
#endif
+ assert( pItem->pSelect && (pItem->pSelect->selFlags & SF_PushDown)!=0 );
}else{
SELECTTRACE(0x100,pParse,p,("Push-down not possible\n"));
}
@@ -135389,16 +138607,18 @@ SQLITE_PRIVATE int sqlite3Select(
/* Generate code to implement the subquery
**
- ** The subquery is implemented as a co-routine if the subquery is
- ** guaranteed to be the outer loop (so that it does not need to be
- ** computed more than once)
+ ** The subquery is implemented as a co-routine if:
+ ** (1) the subquery is guaranteed to be the outer loop (so that
+ ** it does not need to be computed more than once), and
+ ** (2) the subquery is not a CTE that should be materialized
**
- ** TODO: Are there other reasons beside (1) to use a co-routine
+ ** TODO: Are there other reasons beside (1) and (2) to use a co-routine
** implementation?
*/
if( i==0
&& (pTabList->nSrc==1
|| (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */
+ && (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) /* (2) */
){
/* Implement a co-routine that will return a single row of the result
** set on each invocation.
@@ -135407,10 +138627,10 @@ SQLITE_PRIVATE int sqlite3Select(
pItem->regReturn = ++pParse->nMem;
sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop);
- VdbeComment((v, "%s", pItem->pTab->zName));
+ VdbeComment((v, "%!S", pItem));
pItem->addrFillSub = addrTop;
sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn);
- ExplainQueryPlan((pParse, 1, "CO-ROUTINE %u", pSub->selId));
+ ExplainQueryPlan((pParse, 1, "CO-ROUTINE %!S", pItem));
sqlite3Select(pParse, pSub, &dest);
pItem->pTab->nRowLogEst = pSub->nSelectRow;
pItem->fg.viaCoroutine = 1;
@@ -135418,18 +138638,33 @@ SQLITE_PRIVATE int sqlite3Select(
sqlite3VdbeEndCoroutine(v, pItem->regReturn);
sqlite3VdbeJumpHere(v, addrTop-1);
sqlite3ClearTempRegCache(pParse);
- }else{
- /* Generate a subroutine that will fill an ephemeral table with
- ** the content of this subquery. pItem->addrFillSub will point
- ** to the address of the generated subroutine. pItem->regReturn
- ** is a register allocated to hold the subroutine return address
- */
+ }else if( pItem->fg.isCte && pItem->u2.pCteUse->addrM9e>0 ){
+ /* This is a CTE for which materialization code has already been
+ ** generated. Invoke the subroutine to compute the materialization,
+ ** the make the pItem->iCursor be a copy of the ephemerial table that
+ ** holds the result of the materialization. */
+ CteUse *pCteUse = pItem->u2.pCteUse;
+ sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e);
+ if( pItem->iCursor!=pCteUse->iCur ){
+ sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur);
+ }
+ pSub->nSelectRow = pCteUse->nRowEst;
+ }else if( (pPrior = isSelfJoinView(pTabList, pItem))!=0 ){
+ /* This view has already been materialized by a prior entry in
+ ** this same FROM clause. Reuse it. */
+ if( pPrior->addrFillSub ){
+ sqlite3VdbeAddOp2(v, OP_Gosub, pPrior->regReturn, pPrior->addrFillSub);
+ }
+ sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor);
+ pSub->nSelectRow = pPrior->pSelect->nSelectRow;
+ }else{
+ /* Materialize the view. If the view is not correlated, generate a
+ ** subroutine to do the materialization so that subsequent uses of
+ ** the same view can reuse the materialization. */
int topAddr;
int onceAddr = 0;
int retAddr;
- struct SrcList_item *pPrior;
- testcase( pItem->addrFillSub==0 ); /* Ticket c52b09c7f38903b1311 */
pItem->regReturn = ++pParse->nMem;
topAddr = sqlite3VdbeAddOp2(v, OP_Integer, 0, pItem->regReturn);
pItem->addrFillSub = topAddr+1;
@@ -135438,26 +138673,26 @@ SQLITE_PRIVATE int sqlite3Select(
** a trigger, then we only need to compute the value of the subquery
** once. */
onceAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
- VdbeComment((v, "materialize \"%s\"", pItem->pTab->zName));
+ VdbeComment((v, "materialize %!S", pItem));
}else{
- VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName));
- }
- pPrior = isSelfJoinView(pTabList, pItem);
- if( pPrior ){
- sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor);
- assert( pPrior->pSelect!=0 );
- pSub->nSelectRow = pPrior->pSelect->nSelectRow;
- }else{
- sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
- ExplainQueryPlan((pParse, 1, "MATERIALIZE %u", pSub->selId));
- sqlite3Select(pParse, pSub, &dest);
+ VdbeNoopComment((v, "materialize %!S", pItem));
}
+ sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
+ ExplainQueryPlan((pParse, 1, "MATERIALIZE %!S", pItem));
+ sqlite3Select(pParse, pSub, &dest);
pItem->pTab->nRowLogEst = pSub->nSelectRow;
if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr);
retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn);
- VdbeComment((v, "end %s", pItem->pTab->zName));
+ VdbeComment((v, "end %!S", pItem));
sqlite3VdbeChangeP1(v, topAddr, retAddr);
sqlite3ClearTempRegCache(pParse);
+ if( pItem->fg.isCte && pItem->fg.isCorrelated==0 ){
+ CteUse *pCteUse = pItem->u2.pCteUse;
+ pCteUse->addrM9e = pItem->addrFillSub;
+ pCteUse->regRtn = pItem->regReturn;
+ pCteUse->iCur = pItem->iCursor;
+ pCteUse->nRowEst = pSub->nSelectRow;
+ }
}
if( db->mallocFailed ) goto select_end;
pParse->nHeight -= sqlite3SelectExprHeight(p);
@@ -135474,7 +138709,7 @@ SQLITE_PRIVATE int sqlite3Select(
sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0;
#if SELECTTRACE_ENABLED
- if( sqlite3_unsupported_selecttrace & 0x400 ){
+ if( sqlite3SelectTrace & 0x400 ){
SELECTTRACE(0x400,pParse,p,("After all FROM-clause analysis:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -135510,7 +138745,7 @@ SQLITE_PRIVATE int sqlite3Select(
assert( sDistinct.isTnct );
#if SELECTTRACE_ENABLED
- if( sqlite3_unsupported_selecttrace & 0x400 ){
+ if( sqlite3SelectTrace & 0x400 ){
SELECTTRACE(0x400,pParse,p,("Transform DISTINCT into GROUP BY:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
@@ -135602,6 +138837,7 @@ SQLITE_PRIVATE int sqlite3Select(
sSort.pOrderBy = 0;
}
}
+ SELECTTRACE(1,pParse,p,("WhereBegin returns\n"));
/* If sorting index that was created by a prior OP_OpenEphemeral
** instruction ended up not being needed, then change the OP_OpenEphemeral
@@ -135640,6 +138876,7 @@ SQLITE_PRIVATE int sqlite3Select(
/* End the database scan loop.
*/
+ SELECTTRACE(1,pParse,p,("WhereEnd\n"));
sqlite3WhereEnd(pWInfo);
}
}else{
@@ -135710,11 +138947,14 @@ SQLITE_PRIVATE int sqlite3Select(
** SELECT statement.
*/
pAggInfo = sqlite3DbMallocZero(db, sizeof(*pAggInfo) );
- if( pAggInfo==0 ){
+ if( pAggInfo ){
+ sqlite3ParserAddCleanup(pParse,
+ (void(*)(sqlite3*,void*))agginfoFree, pAggInfo);
+ testcase( pParse->earlyCleanup );
+ }
+ if( db->mallocFailed ){
goto select_end;
}
- pAggInfo->pNext = pParse->pAggList;
- pParse->pAggList = pAggInfo;
pAggInfo->selId = p->selId;
memset(&sNC, 0, sizeof(sNC));
sNC.pParse = pParse;
@@ -135758,10 +138998,14 @@ SQLITE_PRIVATE int sqlite3Select(
pAggInfo->mxReg = pParse->nMem;
if( db->mallocFailed ) goto select_end;
#if SELECTTRACE_ENABLED
- if( sqlite3_unsupported_selecttrace & 0x400 ){
+ if( sqlite3SelectTrace & 0x400 ){
int ii;
SELECTTRACE(0x400,pParse,p,("After aggregate analysis %p:\n", pAggInfo));
sqlite3TreeViewSelect(0, p, 0);
+ if( minMaxFlag ){
+ sqlite3DebugPrintf("MIN/MAX Optimization (0x%02x) adds:\n", minMaxFlag);
+ sqlite3TreeViewExprList(0, pMinMaxOrderBy, 0, "ORDERBY");
+ }
for(ii=0; ii<pAggInfo->nColumn; ii++){
sqlite3DebugPrintf("agg-column[%d] iMem=%d\n",
ii, pAggInfo->aCol[ii].iMem);
@@ -135789,6 +139033,20 @@ SQLITE_PRIVATE int sqlite3Select(
int addrSortingIdx; /* The OP_OpenEphemeral for the sorting index */
int addrReset; /* Subroutine for resetting the accumulator */
int regReset; /* Return address register for reset subroutine */
+ ExprList *pDistinct = 0;
+ u16 distFlag = 0;
+ int eDist = WHERE_DISTINCT_NOOP;
+
+ if( pAggInfo->nFunc==1
+ && pAggInfo->aFunc[0].iDistinct>=0
+ && pAggInfo->aFunc[0].pFExpr->x.pList
+ ){
+ Expr *pExpr = pAggInfo->aFunc[0].pFExpr->x.pList->a[0].pExpr;
+ pExpr = sqlite3ExprDup(db, pExpr, 0);
+ pDistinct = sqlite3ExprListDup(db, pGroupBy, 0);
+ pDistinct = sqlite3ExprListAppend(pParse, pDistinct, pExpr);
+ distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0;
+ }
/* If there is a GROUP BY clause we might need a sorting index to
** implement it. Allocate that sorting index now. If it turns out
@@ -135825,10 +139083,15 @@ SQLITE_PRIVATE int sqlite3Select(
*/
sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
SELECTTRACE(1,pParse,p,("WhereBegin\n"));
- pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0,
- WHERE_GROUPBY | (orderByGrp ? WHERE_SORTBYGROUP : 0), 0
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct,
+ WHERE_GROUPBY | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0
);
- if( pWInfo==0 ) goto select_end;
+ if( pWInfo==0 ){
+ sqlite3ExprListDelete(db, pDistinct);
+ goto select_end;
+ }
+ eDist = sqlite3WhereIsDistinct(pWInfo);
+ SELECTTRACE(1,pParse,p,("WhereBegin returns\n"));
if( sqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){
/* The optimizer is able to deliver rows in group by order so
** we do not have to sort. The OP_OpenEphemeral table will be
@@ -135877,6 +139140,7 @@ SQLITE_PRIVATE int sqlite3Select(
sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord);
sqlite3ReleaseTempReg(pParse, regRecord);
sqlite3ReleaseTempRange(pParse, regBase, nCol);
+ SELECTTRACE(1,pParse,p,("WhereEnd\n"));
sqlite3WhereEnd(pWInfo);
pAggInfo->sortingIdxPTab = sortPTab = pParse->nTab++;
sortOut = sqlite3GetTempReg(pParse);
@@ -135944,19 +139208,21 @@ SQLITE_PRIVATE int sqlite3Select(
** the current row
*/
sqlite3VdbeJumpHere(v, addr1);
- updateAccumulator(pParse, iUseFlag, pAggInfo);
+ updateAccumulator(pParse, iUseFlag, pAggInfo, eDist);
sqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag);
VdbeComment((v, "indicate data in accumulator"));
/* End of the loop
*/
if( groupBySort ){
- sqlite3VdbeAddOp2(v, OP_SorterNext, pAggInfo->sortingIdx, addrTopOfLoop);
+ sqlite3VdbeAddOp2(v, OP_SorterNext, pAggInfo->sortingIdx,addrTopOfLoop);
VdbeCoverage(v);
}else{
+ SELECTTRACE(1,pParse,p,("WhereEnd\n"));
sqlite3WhereEnd(pWInfo);
sqlite3VdbeChangeToNoop(v, addrSortingIdx);
}
+ sqlite3ExprListDelete(db, pDistinct);
/* Output the final row of result
*/
@@ -136000,6 +139266,10 @@ SQLITE_PRIVATE int sqlite3Select(
VdbeComment((v, "indicate accumulator empty"));
sqlite3VdbeAddOp1(v, OP_Return, regReset);
+ if( eDist!=WHERE_DISTINCT_NOOP ){
+ struct AggInfo_func *pF = &pAggInfo->aFunc[0];
+ fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr);
+ }
} /* endif pGroupBy. Begin aggregate queries without GROUP BY: */
else {
Table *pTab;
@@ -136063,7 +139333,9 @@ SQLITE_PRIVATE int sqlite3Select(
explainSimpleCount(pParse, pTab, pBest);
}else{
int regAcc = 0; /* "populate accumulators" flag */
- int addrSkip;
+ ExprList *pDistinct = 0;
+ u16 distFlag = 0;
+ int eDist;
/* If there are accumulator registers but no min() or max() functions
** without FILTER clauses, allocate register regAcc. Register regAcc
@@ -136087,6 +139359,9 @@ SQLITE_PRIVATE int sqlite3Select(
regAcc = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Integer, 0, regAcc);
}
+ }else if( pAggInfo->nFunc==1 && pAggInfo->aFunc[0].iDistinct>=0 ){
+ pDistinct = pAggInfo->aFunc[0].pFExpr->x.pList;
+ distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0;
}
/* This case runs if the aggregate has no GROUP BY clause. The
@@ -136106,16 +139381,23 @@ SQLITE_PRIVATE int sqlite3Select(
SELECTTRACE(1,pParse,p,("WhereBegin\n"));
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy,
- 0, minMaxFlag, 0);
+ pDistinct, minMaxFlag|distFlag, 0);
if( pWInfo==0 ){
goto select_end;
}
- updateAccumulator(pParse, regAcc, pAggInfo);
+ SELECTTRACE(1,pParse,p,("WhereBegin returns\n"));
+ eDist = sqlite3WhereIsDistinct(pWInfo);
+ updateAccumulator(pParse, regAcc, pAggInfo, eDist);
+ if( eDist!=WHERE_DISTINCT_NOOP ){
+ struct AggInfo_func *pF = &pAggInfo->aFunc[0];
+ fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr);
+ }
+
if( regAcc ) sqlite3VdbeAddOp2(v, OP_Integer, 1, regAcc);
- addrSkip = sqlite3WhereOrderByLimitOptLabel(pWInfo);
- if( addrSkip!=sqlite3WhereContinueLabel(pWInfo) ){
- sqlite3VdbeGoto(v, addrSkip);
+ if( minMaxFlag ){
+ sqlite3WhereMinMaxOptEarlyOut(v, pWInfo);
}
+ SELECTTRACE(1,pParse,p,("WhereEnd\n"));
sqlite3WhereEnd(pWInfo);
finalizeAggFunctions(pParse, pAggInfo);
}
@@ -136155,20 +139437,20 @@ SQLITE_PRIVATE int sqlite3Select(
** successful coding of the SELECT.
*/
select_end:
+ assert( db->mallocFailed==0 || db->mallocFailed==1 );
+ pParse->nErr += db->mallocFailed;
sqlite3ExprListDelete(db, pMinMaxOrderBy);
#ifdef SQLITE_DEBUG
if( pAggInfo && !db->mallocFailed ){
for(i=0; i<pAggInfo->nColumn; i++){
Expr *pExpr = pAggInfo->aCol[i].pCExpr;
- assert( pExpr!=0 || db->mallocFailed );
- if( pExpr==0 ) continue;
+ assert( pExpr!=0 );
assert( pExpr->pAggInfo==pAggInfo );
assert( pExpr->iAgg==i );
}
for(i=0; i<pAggInfo->nFunc; i++){
Expr *pExpr = pAggInfo->aFunc[i].pFExpr;
- assert( pExpr!=0 || db->mallocFailed );
- if( pExpr==0 ) continue;
+ assert( pExpr!=0 );
assert( pExpr->pAggInfo==pAggInfo );
assert( pExpr->iAgg==i );
}
@@ -136177,7 +139459,7 @@ select_end:
#if SELECTTRACE_ENABLED
SELECTTRACE(0x1,pParse,p,("end processing\n"));
- if( (sqlite3_unsupported_selecttrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
+ if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
sqlite3TreeViewSelect(0, p, 0);
}
#endif
@@ -136438,28 +139720,51 @@ SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerS
** pTab as well as the triggers lised in pTab->pTrigger.
*/
SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
- Schema * const pTmpSchema = pParse->db->aDb[1].pSchema;
- Trigger *pList = 0; /* List of triggers to return */
+ Schema *pTmpSchema; /* Schema of the pTab table */
+ Trigger *pList; /* List of triggers to return */
+ HashElem *p; /* Loop variable for TEMP triggers */
if( pParse->disableTriggers ){
return 0;
}
-
- if( pTmpSchema!=pTab->pSchema ){
- HashElem *p;
- assert( sqlite3SchemaMutexHeld(pParse->db, 0, pTmpSchema) );
- for(p=sqliteHashFirst(&pTmpSchema->trigHash); p; p=sqliteHashNext(p)){
- Trigger *pTrig = (Trigger *)sqliteHashData(p);
- if( pTrig->pTabSchema==pTab->pSchema
- && 0==sqlite3StrICmp(pTrig->table, pTab->zName)
- ){
- pTrig->pNext = (pList ? pList : pTab->pTrigger);
- pList = pTrig;
- }
+ pTmpSchema = pParse->db->aDb[1].pSchema;
+ p = sqliteHashFirst(&pTmpSchema->trigHash);
+ pList = pTab->pTrigger;
+ while( p ){
+ Trigger *pTrig = (Trigger *)sqliteHashData(p);
+ if( pTrig->pTabSchema==pTab->pSchema
+ && pTrig->table
+ && 0==sqlite3StrICmp(pTrig->table, pTab->zName)
+ && pTrig->pTabSchema!=pTmpSchema
+ ){
+ pTrig->pNext = pList;
+ pList = pTrig;
+ }else if( pTrig->op==TK_RETURNING
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ && pParse->db->pVtabCtx==0
+#endif
+ ){
+ assert( pParse->bReturning );
+ assert( &(pParse->u1.pReturning->retTrig) == pTrig );
+ pTrig->table = pTab->zName;
+ pTrig->pTabSchema = pTab->pSchema;
+ pTrig->pNext = pList;
+ pList = pTrig;
}
+ p = sqliteHashNext(p);
}
-
- return (pList ? pList : pTab->pTrigger);
+#if 0
+ if( pList ){
+ Trigger *pX;
+ printf("Triggers for %s:", pTab->zName);
+ for(pX=pList; pX; pX=pX->pNext){
+ printf(" %s", pX->zName);
+ }
+ printf("\n");
+ fflush(stdout);
+ }
+#endif
+ return pList;
}
/*
@@ -136547,22 +139852,11 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
pTab = sqlite3SrcListLookup(pParse, pTableName);
if( !pTab ){
/* The table does not exist. */
- if( db->init.iDb==1 ){
- /* Ticket #3810.
- ** Normally, whenever a table is dropped, all associated triggers are
- ** dropped too. But if a TEMP trigger is created on a non-TEMP table
- ** and the table is dropped by a different database connection, the
- ** trigger is not visible to the database connection that does the
- ** drop so the trigger cannot be dropped. This results in an
- ** "orphaned trigger" - a trigger whose associated table is missing.
- */
- db->init.orphanTrigger = 1;
- }
- goto trigger_cleanup;
+ goto trigger_orphan_error;
}
if( IsVirtual(pTab) ){
sqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables");
- goto trigger_cleanup;
+ goto trigger_orphan_error;
}
/* Check that the trigger name is not reserved and that no trigger of the
@@ -136599,13 +139893,13 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
*/
if( pTab->pSelect && tr_tm!=TK_INSTEAD ){
sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S",
- (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0);
- goto trigger_cleanup;
+ (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName->a);
+ goto trigger_orphan_error;
}
if( !pTab->pSelect && tr_tm==TK_INSTEAD ){
sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF"
- " trigger on table: %S", pTableName, 0);
- goto trigger_cleanup;
+ " trigger on table: %S", pTableName->a);
+ goto trigger_orphan_error;
}
#ifndef SQLITE_OMIT_AUTHORIZATION
@@ -136665,6 +139959,23 @@ trigger_cleanup:
}else{
assert( pParse->pNewTrigger==pTrigger );
}
+ return;
+
+trigger_orphan_error:
+ if( db->init.iDb==1 ){
+ /* Ticket #3810.
+ ** Normally, whenever a table is dropped, all associated triggers are
+ ** dropped too. But if a TEMP trigger is created on a non-TEMP table
+ ** and the table is dropped by a different database connection, the
+ ** trigger is not visible to the database connection that does the
+ ** drop so the trigger cannot be dropped. This results in an
+ ** "orphaned trigger" - a trigger whose associated table is missing.
+ **
+ ** 2020-11-05 see also https://sqlite.org/forum/forumpost/157dc791df
+ */
+ db->init.orphanTrigger = 1;
+ }
+ goto trigger_cleanup;
}
/*
@@ -136729,7 +140040,7 @@ SQLITE_PRIVATE void sqlite3FinishTrigger(
sqlite3DbFree(db, z);
sqlite3ChangeCookie(pParse, iDb);
sqlite3VdbeAddParseSchemaOp(v, iDb,
- sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName));
+ sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName), 0);
}
if( db->init.busy ){
@@ -136942,7 +140253,7 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(
** Recursively delete a Trigger structure
*/
SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){
- if( pTrigger==0 ) return;
+ if( pTrigger==0 || pTrigger->bReturning ) return;
sqlite3DeleteTriggerStep(db, pTrigger->step_list);
sqlite3DbFree(db, pTrigger->zName);
sqlite3DbFree(db, pTrigger->table);
@@ -136984,7 +140295,7 @@ SQLITE_PRIVATE void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr)
}
if( !pTrigger ){
if( !noErr ){
- sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0);
+ sqlite3ErrorMsg(pParse, "no such trigger: %S", pName->a);
}else{
sqlite3CodeVerifyNamedSchema(pParse, zDb);
}
@@ -137107,15 +140418,53 @@ SQLITE_PRIVATE Trigger *sqlite3TriggersExist(
Trigger *pList = 0;
Trigger *p;
- if( (pParse->db->flags & SQLITE_EnableTrigger)!=0 ){
- pList = sqlite3TriggerList(pParse, pTab);
- }
- assert( pList==0 || IsVirtual(pTab)==0 );
- for(p=pList; p; p=p->pNext){
- if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){
- mask |= p->tr_tm;
+ pList = sqlite3TriggerList(pParse, pTab);
+ assert( pList==0 || IsVirtual(pTab)==0
+ || (pList->bReturning && pList->pNext==0) );
+ if( pList!=0 ){
+ p = pList;
+ if( (pParse->db->flags & SQLITE_EnableTrigger)==0
+ && pTab->pTrigger!=0
+ ){
+ /* The SQLITE_DBCONFIG_ENABLE_TRIGGER setting is off. That means that
+ ** only TEMP triggers are allowed. Truncate the pList so that it
+ ** includes only TEMP triggers */
+ if( pList==pTab->pTrigger ){
+ pList = 0;
+ goto exit_triggers_exist;
+ }
+ while( ALWAYS(p->pNext) && p->pNext!=pTab->pTrigger ) p = p->pNext;
+ p->pNext = 0;
+ p = pList;
}
+ do{
+ if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){
+ mask |= p->tr_tm;
+ }else if( p->op==TK_RETURNING ){
+ /* The first time a RETURNING trigger is seen, the "op" value tells
+ ** us what time of trigger it should be. */
+ assert( sqlite3IsToplevel(pParse) );
+ p->op = op;
+ if( IsVirtual(pTab) ){
+ if( op!=TK_INSERT ){
+ sqlite3ErrorMsg(pParse,
+ "%s RETURNING is not available on virtual tables",
+ op==TK_DELETE ? "DELETE" : "UPDATE");
+ }
+ p->tr_tm = TRIGGER_BEFORE;
+ }else{
+ p->tr_tm = TRIGGER_AFTER;
+ }
+ mask |= p->tr_tm;
+ }else if( p->bReturning && p->op==TK_INSERT && op==TK_UPDATE
+ && sqlite3IsToplevel(pParse) ){
+ /* Also fire a RETURNING trigger for an UPSERT */
+ mask |= p->tr_tm;
+ }
+ p = p->pNext;
+ }while( p );
}
+exit_triggers_exist:
if( pMask ){
*pMask = mask;
}
@@ -137159,6 +140508,137 @@ SQLITE_PRIVATE SrcList *sqlite3TriggerStepSrc(
}
/*
+** Return true if the pExpr term from the RETURNING clause argument
+** list is of the form "*". Raise an error if the terms if of the
+** form "table.*".
+*/
+static int isAsteriskTerm(
+ Parse *pParse, /* Parsing context */
+ Expr *pTerm /* A term in the RETURNING clause */
+){
+ assert( pTerm!=0 );
+ if( pTerm->op==TK_ASTERISK ) return 1;
+ if( pTerm->op!=TK_DOT ) return 0;
+ assert( pTerm->pRight!=0 );
+ assert( pTerm->pLeft!=0 );
+ if( pTerm->pRight->op!=TK_ASTERISK ) return 0;
+ sqlite3ErrorMsg(pParse, "RETURNING may not use \"TABLE.*\" wildcards");
+ return 1;
+}
+
+/* The input list pList is the list of result set terms from a RETURNING
+** clause. The table that we are returning from is pTab.
+**
+** This routine makes a copy of the pList, and at the same time expands
+** any "*" wildcards to be the complete set of columns from pTab.
+*/
+static ExprList *sqlite3ExpandReturning(
+ Parse *pParse, /* Parsing context */
+ ExprList *pList, /* The arguments to RETURNING */
+ Table *pTab /* The table being updated */
+){
+ ExprList *pNew = 0;
+ sqlite3 *db = pParse->db;
+ int i;
+
+ for(i=0; i<pList->nExpr; i++){
+ Expr *pOldExpr = pList->a[i].pExpr;
+ if( NEVER(pOldExpr==0) ) continue;
+ if( isAsteriskTerm(pParse, pOldExpr) ){
+ int jj;
+ for(jj=0; jj<pTab->nCol; jj++){
+ Expr *pNewExpr;
+ if( IsHiddenColumn(pTab->aCol+jj) ) continue;
+ pNewExpr = sqlite3Expr(db, TK_ID, pTab->aCol[jj].zName);
+ pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr);
+ if( !db->mallocFailed ){
+ struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1];
+ pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zName);
+ pItem->eEName = ENAME_NAME;
+ }
+ }
+ }else{
+ Expr *pNewExpr = sqlite3ExprDup(db, pOldExpr, 0);
+ pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr);
+ if( !db->mallocFailed && ALWAYS(pList->a[i].zEName!=0) ){
+ struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1];
+ pItem->zEName = sqlite3DbStrDup(db, pList->a[i].zEName);
+ pItem->eEName = pList->a[i].eEName;
+ }
+ }
+ }
+ return pNew;
+}
+
+/*
+** Generate code for the RETURNING trigger. Unlike other triggers
+** that invoke a subprogram in the bytecode, the code for RETURNING
+** is generated in-line.
+*/
+static void codeReturningTrigger(
+ Parse *pParse, /* Parse context */
+ Trigger *pTrigger, /* The trigger step that defines the RETURNING */
+ Table *pTab, /* The table to code triggers from */
+ int regIn /* The first in an array of registers */
+){
+ Vdbe *v = pParse->pVdbe;
+ sqlite3 *db = pParse->db;
+ ExprList *pNew;
+ Returning *pReturning;
+ Select sSelect;
+ SrcList sFrom;
+
+ assert( v!=0 );
+ assert( pParse->bReturning );
+ pReturning = pParse->u1.pReturning;
+ assert( pTrigger == &(pReturning->retTrig) );
+ memset(&sSelect, 0, sizeof(sSelect));
+ memset(&sFrom, 0, sizeof(sFrom));
+ sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0);
+ sSelect.pSrc = &sFrom;
+ sFrom.nSrc = 1;
+ sFrom.a[0].pTab = pTab;
+ sqlite3SelectPrep(pParse, &sSelect, 0);
+ if( db->mallocFailed==0 && pParse->nErr==0 ){
+ sqlite3GenerateColumnNames(pParse, &sSelect);
+ }
+ sqlite3ExprListDelete(db, sSelect.pEList);
+ pNew = sqlite3ExpandReturning(pParse, pReturning->pReturnEL, pTab);
+ if( pNew ){
+ NameContext sNC;
+ memset(&sNC, 0, sizeof(sNC));
+ if( pReturning->nRetCol==0 ){
+ pReturning->nRetCol = pNew->nExpr;
+ pReturning->iRetCur = pParse->nTab++;
+ }
+ sNC.pParse = pParse;
+ sNC.uNC.iBaseReg = regIn;
+ sNC.ncFlags = NC_UBaseReg;
+ pParse->eTriggerOp = pTrigger->op;
+ pParse->pTriggerTab = pTab;
+ if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK ){
+ int i;
+ int nCol = pNew->nExpr;
+ int reg = pParse->nMem+1;
+ pParse->nMem += nCol+2;
+ pReturning->iRetReg = reg;
+ for(i=0; i<nCol; i++){
+ Expr *pCol = pNew->a[i].pExpr;
+ sqlite3ExprCodeFactorable(pParse, pCol, reg+i);
+ }
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, i, reg+i);
+ sqlite3VdbeAddOp2(v, OP_NewRowid, pReturning->iRetCur, reg+i+1);
+ sqlite3VdbeAddOp3(v, OP_Insert, pReturning->iRetCur, reg+i, reg+i+1);
+ }
+ sqlite3ExprListDelete(db, pNew);
+ pParse->eTriggerOp = 0;
+ pParse->pTriggerTab = 0;
+ }
+}
+
+
+
+/*
** Generate VDBE code for the statements inside the body of a single
** trigger.
*/
@@ -137207,6 +140687,7 @@ static int codeTriggerProgram(
sqlite3ExprDup(db, pStep->pWhere, 0),
pParse->eOrconf, 0, 0, 0
);
+ sqlite3VdbeAddOp0(v, OP_ResetCount);
break;
}
case TK_INSERT: {
@@ -137217,6 +140698,7 @@ static int codeTriggerProgram(
pParse->eOrconf,
sqlite3UpsertDup(db, pStep->pUpsert)
);
+ sqlite3VdbeAddOp0(v, OP_ResetCount);
break;
}
case TK_DELETE: {
@@ -137224,6 +140706,7 @@ static int codeTriggerProgram(
sqlite3TriggerStepSrc(pParse, pStep),
sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0
);
+ sqlite3VdbeAddOp0(v, OP_ResetCount);
break;
}
default: assert( pStep->op==TK_SELECT ); {
@@ -137235,9 +140718,6 @@ static int codeTriggerProgram(
break;
}
}
- if( pStep->op!=TK_SELECT ){
- sqlite3VdbeAddOp0(v, OP_ResetCount);
- }
}
return 0;
@@ -137353,8 +140833,8 @@ static TriggerPrg *codeRowTrigger(
** OP_Halt inserted at the end of the program. */
if( pTrigger->pWhen ){
pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0);
- if( SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen)
- && db->mallocFailed==0
+ if( db->mallocFailed==0
+ && SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen)
){
iEndTrigger = sqlite3VdbeMakeLabel(pSubParse);
sqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL);
@@ -137384,7 +140864,6 @@ static TriggerPrg *codeRowTrigger(
sqlite3VdbeDelete(v);
}
- assert( !pSubParse->pAinc && !pSubParse->pZombieTab );
assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg );
sqlite3ParserReset(pSubParse);
sqlite3StackFree(db, pSubParse);
@@ -137486,7 +140965,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(
** ... ...
** reg+N OLD.* value of right-most column of pTab
** reg+N+1 NEW.rowid
-** reg+N+2 OLD.* value of left-most column of pTab
+** reg+N+2 NEW.* value of left-most column of pTab
** ... ...
** reg+N+N+1 NEW.* value of right-most column of pTab
**
@@ -137531,12 +141010,20 @@ SQLITE_PRIVATE void sqlite3CodeRowTrigger(
assert( p->pSchema==p->pTabSchema
|| p->pSchema==pParse->db->aDb[1].pSchema );
- /* Determine whether we should code this trigger */
- if( p->op==op
+ /* Determine whether we should code this trigger. One of two choices:
+ ** 1. The trigger is an exact match to the current DML statement
+ ** 2. This is a RETURNING trigger for INSERT but we are currently
+ ** doing the UPDATE part of an UPSERT.
+ */
+ if( (p->op==op || (p->bReturning && p->op==TK_INSERT && op==TK_UPDATE))
&& p->tr_tm==tr_tm
&& checkColumnOverlap(p->pColumns, pChanges)
){
- sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump);
+ if( !p->bReturning ){
+ sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump);
+ }else if( sqlite3IsToplevel(pParse) ){
+ codeReturningTrigger(pParse, p, pTab, reg);
+ }
}
}
}
@@ -137581,13 +141068,18 @@ SQLITE_PRIVATE u32 sqlite3TriggerColmask(
assert( isNew==1 || isNew==0 );
for(p=pTrigger; p; p=p->pNext){
- if( p->op==op && (tr_tm&p->tr_tm)
+ if( p->op==op
+ && (tr_tm&p->tr_tm)
&& checkColumnOverlap(p->pColumns,pChanges)
){
- TriggerPrg *pPrg;
- pPrg = getRowTrigger(pParse, p, pTab, orconf);
- if( pPrg ){
- mask |= pPrg->aColmask[isNew];
+ if( p->bReturning ){
+ mask = 0xffffffff;
+ }else{
+ TriggerPrg *pPrg;
+ pPrg = getRowTrigger(pParse, p, pTab, orconf);
+ if( pPrg ){
+ mask |= pPrg->aColmask[isNew];
+ }
}
}
}
@@ -137821,6 +141313,7 @@ static void updateFromSelect(
assert( pTabList->nSrc>1 );
if( pSrc ){
+ pSrc->a[0].fg.notCte = 1;
pSrc->a[0].iCursor = -1;
pSrc->a[0].pTab->nTabRef--;
pSrc->a[0].pTab = 0;
@@ -137835,7 +141328,7 @@ static void updateFromSelect(
#endif
pList = sqlite3ExprListAppend(pParse, pList, pNew);
}
- eDest = SRT_Upfrom;
+ eDest = IsVirtual(pTab) ? SRT_Table : SRT_Upfrom;
}else if( pTab->pSelect ){
for(i=0; i<pTab->nCol; i++){
pList = sqlite3ExprListAppend(pParse, pList, exprRowColumn(pParse, i));
@@ -137850,7 +141343,8 @@ static void updateFromSelect(
}
#endif
}
- if( ALWAYS(pChanges) ){
+ assert( pChanges!=0 || pParse->db->mallocFailed );
+ if( pChanges ){
for(i=0; i<pChanges->nExpr; i++){
pList = sqlite3ExprListAppend(pParse, pList,
sqlite3ExprDup(db, pChanges->a[i].pExpr, 0)
@@ -138244,6 +141738,7 @@ SQLITE_PRIVATE void sqlite3Update(
if( (db->flags&SQLITE_CountRows)!=0
&& !pParse->pTriggerTab
&& !pParse->nested
+ && !pParse->bReturning
&& pUpsert==0
){
regRowCount = ++pParse->nMem;
@@ -138252,6 +141747,8 @@ SQLITE_PRIVATE void sqlite3Update(
if( nChangeFrom==0 && HasRowid(pTab) ){
sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid);
+ iEph = pParse->nTab++;
+ addrOpen = sqlite3VdbeAddOp3(v, OP_OpenEphemeral, iEph, 0, regRowSet);
}else{
assert( pPk!=0 || HasRowid(pTab) );
nPk = pPk ? pPk->nKeyCol : 0;
@@ -138306,7 +141803,7 @@ SQLITE_PRIVATE void sqlite3Update(
** be deleted as a result of REPLACE conflict handling. Any of these
** things might disturb a cursor being used to scan through the table
** or index, causing a single-pass approach to malfunction. */
- flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE;
+ flags = WHERE_ONEPASS_DESIRED;
if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
flags |= WHERE_ONEPASS_MULTIROW;
}
@@ -138343,9 +141840,10 @@ SQLITE_PRIVATE void sqlite3Update(
** leave it in register regOldRowid. */
sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid);
if( eOnePass==ONEPASS_OFF ){
- /* We need to use regRowSet, so reallocate aRegIdx[nAllIdx] */
aRegIdx[nAllIdx] = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid);
+ sqlite3VdbeAddOp3(v, OP_Insert, iEph, regRowSet, regOldRowid);
+ }else{
+ if( ALWAYS(addrOpen) ) sqlite3VdbeChangeToNoop(v, addrOpen);
}
}else{
/* Read the PK of the current row into an array of registers. In
@@ -138396,7 +141894,12 @@ SQLITE_PRIVATE void sqlite3Update(
/* Top of the update loop */
if( eOnePass!=ONEPASS_OFF ){
- if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){
+ if( aiCurOnePass[0]!=iDataCur
+ && aiCurOnePass[1]!=iDataCur
+#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
+ && !isView
+#endif
+ ){
assert( pPk );
sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey,nKey);
VdbeCoverage(v);
@@ -138433,8 +141936,9 @@ SQLITE_PRIVATE void sqlite3Update(
VdbeCoverage(v);
}
}else{
- labelContinue = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet,labelBreak,
- regOldRowid);
+ sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v);
+ labelContinue = sqlite3VdbeMakeLabel(pParse);
+ addrTop = sqlite3VdbeAddOp2(v, OP_Rowid, iEph, regOldRowid);
VdbeCoverage(v);
sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
VdbeCoverage(v);
@@ -138684,11 +142188,9 @@ SQLITE_PRIVATE void sqlite3Update(
}else if( eOnePass==ONEPASS_MULTI ){
sqlite3VdbeResolveLabel(v, labelContinue);
sqlite3WhereEnd(pWInfo);
- }else if( pPk || nChangeFrom ){
+ }else{
sqlite3VdbeResolveLabel(v, labelContinue);
sqlite3VdbeAddOp2(v, OP_Next, iEph, addrTop); VdbeCoverage(v);
- }else{
- sqlite3VdbeGoto(v, labelContinue);
}
sqlite3VdbeResolveLabel(v, labelBreak);
@@ -138705,7 +142207,7 @@ SQLITE_PRIVATE void sqlite3Update(
** that information.
*/
if( regRowCount ){
- sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
+ sqlite3VdbeAddOp2(v, OP_ChngCntRow, regRowCount, 1);
sqlite3VdbeSetNumCols(v, 1);
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC);
}
@@ -138788,12 +142290,26 @@ static void updateVirtualTable(
regArg = pParse->nMem + 1;
pParse->nMem += nArg;
if( pSrc->nSrc>1 ){
+ Index *pPk = 0;
Expr *pRow;
ExprList *pList;
- if( pRowid ){
- pRow = sqlite3ExprDup(db, pRowid, 0);
+ if( HasRowid(pTab) ){
+ if( pRowid ){
+ pRow = sqlite3ExprDup(db, pRowid, 0);
+ }else{
+ pRow = sqlite3PExpr(pParse, TK_ROW, 0, 0);
+ }
}else{
- pRow = sqlite3PExpr(pParse, TK_ROW, 0, 0);
+ i16 iPk; /* PRIMARY KEY column */
+ pPk = sqlite3PrimaryKeyIndex(pTab);
+ assert( pPk!=0 );
+ assert( pPk->nKeyCol==1 );
+ iPk = pPk->aiColumn[0];
+ if( aXRef[iPk]>=0 ){
+ pRow = sqlite3ExprDup(db, pChanges->a[aXRef[iPk]].pExpr, 0);
+ }else{
+ pRow = exprRowColumn(pParse, iPk);
+ }
}
pList = sqlite3ExprListAppend(pParse, 0, pRow);
@@ -138807,7 +142323,7 @@ static void updateVirtualTable(
}
}
- updateFromSelect(pParse, ephemTab, 0, pList, pSrc, pWhere, 0, 0);
+ updateFromSelect(pParse, ephemTab, pPk, pList, pSrc, pWhere, 0, 0);
sqlite3ExprListDelete(db, pList);
eOnePass = ONEPASS_OFF;
}else{
@@ -138926,16 +142442,23 @@ static void updateVirtualTable(
/*
** Free a list of Upsert objects
*/
-SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){
- if( p ){
+static void SQLITE_NOINLINE upsertDelete(sqlite3 *db, Upsert *p){
+ do{
+ Upsert *pNext = p->pNextUpsert;
sqlite3ExprListDelete(db, p->pUpsertTarget);
sqlite3ExprDelete(db, p->pUpsertTargetWhere);
sqlite3ExprListDelete(db, p->pUpsertSet);
sqlite3ExprDelete(db, p->pUpsertWhere);
+ sqlite3DbFree(db, p->pToFree);
sqlite3DbFree(db, p);
- }
+ p = pNext;
+ }while( p );
+}
+SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){
+ if( p ) upsertDelete(db, p);
}
+
/*
** Duplicate an Upsert object.
*/
@@ -138945,7 +142468,8 @@ SQLITE_PRIVATE Upsert *sqlite3UpsertDup(sqlite3 *db, Upsert *p){
sqlite3ExprListDup(db, p->pUpsertTarget, 0),
sqlite3ExprDup(db, p->pUpsertTargetWhere, 0),
sqlite3ExprListDup(db, p->pUpsertSet, 0),
- sqlite3ExprDup(db, p->pUpsertWhere, 0)
+ sqlite3ExprDup(db, p->pUpsertWhere, 0),
+ sqlite3UpsertDup(db, p->pNextUpsert)
);
}
@@ -138957,22 +142481,25 @@ SQLITE_PRIVATE Upsert *sqlite3UpsertNew(
ExprList *pTarget, /* Target argument to ON CONFLICT, or NULL */
Expr *pTargetWhere, /* Optional WHERE clause on the target */
ExprList *pSet, /* UPDATE columns, or NULL for a DO NOTHING */
- Expr *pWhere /* WHERE clause for the ON CONFLICT UPDATE */
+ Expr *pWhere, /* WHERE clause for the ON CONFLICT UPDATE */
+ Upsert *pNext /* Next ON CONFLICT clause in the list */
){
Upsert *pNew;
- pNew = sqlite3DbMallocRaw(db, sizeof(Upsert));
+ pNew = sqlite3DbMallocZero(db, sizeof(Upsert));
if( pNew==0 ){
sqlite3ExprListDelete(db, pTarget);
sqlite3ExprDelete(db, pTargetWhere);
sqlite3ExprListDelete(db, pSet);
sqlite3ExprDelete(db, pWhere);
+ sqlite3UpsertDelete(db, pNext);
return 0;
}else{
pNew->pUpsertTarget = pTarget;
pNew->pUpsertTargetWhere = pTargetWhere;
pNew->pUpsertSet = pSet;
pNew->pUpsertWhere = pWhere;
- pNew->pUpsertIdx = 0;
+ pNew->isDoUpdate = pSet!=0;
+ pNew->pNextUpsert = pNext;
}
return pNew;
}
@@ -138997,6 +142524,7 @@ SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(
Expr *pTerm; /* One term of the conflict-target clause */
NameContext sNC; /* Context for resolving symbolic names */
Expr sCol[2]; /* Index column converted into an Expr */
+ int nClause = 0; /* Counter of ON CONFLICT clauses */
assert( pTabList->nSrc==1 );
assert( pTabList->a[0].pTab!=0 );
@@ -139010,87 +142538,131 @@ SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(
memset(&sNC, 0, sizeof(sNC));
sNC.pParse = pParse;
sNC.pSrcList = pTabList;
- rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget);
- if( rc ) return rc;
- rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere);
- if( rc ) return rc;
+ for(; pUpsert && pUpsert->pUpsertTarget;
+ pUpsert=pUpsert->pNextUpsert, nClause++){
+ rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget);
+ if( rc ) return rc;
+ rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere);
+ if( rc ) return rc;
- /* Check to see if the conflict target matches the rowid. */
- pTab = pTabList->a[0].pTab;
- pTarget = pUpsert->pUpsertTarget;
- iCursor = pTabList->a[0].iCursor;
- if( HasRowid(pTab)
- && pTarget->nExpr==1
- && (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN
- && pTerm->iColumn==XN_ROWID
- ){
- /* The conflict-target is the rowid of the primary table */
- assert( pUpsert->pUpsertIdx==0 );
- return SQLITE_OK;
- }
+ /* Check to see if the conflict target matches the rowid. */
+ pTab = pTabList->a[0].pTab;
+ pTarget = pUpsert->pUpsertTarget;
+ iCursor = pTabList->a[0].iCursor;
+ if( HasRowid(pTab)
+ && pTarget->nExpr==1
+ && (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN
+ && pTerm->iColumn==XN_ROWID
+ ){
+ /* The conflict-target is the rowid of the primary table */
+ assert( pUpsert->pUpsertIdx==0 );
+ continue;
+ }
- /* Initialize sCol[0..1] to be an expression parse tree for a
- ** single column of an index. The sCol[0] node will be the TK_COLLATE
- ** operator and sCol[1] will be the TK_COLUMN operator. Code below
- ** will populate the specific collation and column number values
- ** prior to comparing against the conflict-target expression.
- */
- memset(sCol, 0, sizeof(sCol));
- sCol[0].op = TK_COLLATE;
- sCol[0].pLeft = &sCol[1];
- sCol[1].op = TK_COLUMN;
- sCol[1].iTable = pTabList->a[0].iCursor;
+ /* Initialize sCol[0..1] to be an expression parse tree for a
+ ** single column of an index. The sCol[0] node will be the TK_COLLATE
+ ** operator and sCol[1] will be the TK_COLUMN operator. Code below
+ ** will populate the specific collation and column number values
+ ** prior to comparing against the conflict-target expression.
+ */
+ memset(sCol, 0, sizeof(sCol));
+ sCol[0].op = TK_COLLATE;
+ sCol[0].pLeft = &sCol[1];
+ sCol[1].op = TK_COLUMN;
+ sCol[1].iTable = pTabList->a[0].iCursor;
- /* Check for matches against other indexes */
- for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
- int ii, jj, nn;
- if( !IsUniqueIndex(pIdx) ) continue;
- if( pTarget->nExpr!=pIdx->nKeyCol ) continue;
- if( pIdx->pPartIdxWhere ){
- if( pUpsert->pUpsertTargetWhere==0 ) continue;
- if( sqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere,
- pIdx->pPartIdxWhere, iCursor)!=0 ){
- continue;
+ /* Check for matches against other indexes */
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ int ii, jj, nn;
+ if( !IsUniqueIndex(pIdx) ) continue;
+ if( pTarget->nExpr!=pIdx->nKeyCol ) continue;
+ if( pIdx->pPartIdxWhere ){
+ if( pUpsert->pUpsertTargetWhere==0 ) continue;
+ if( sqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere,
+ pIdx->pPartIdxWhere, iCursor)!=0 ){
+ continue;
+ }
}
- }
- nn = pIdx->nKeyCol;
- for(ii=0; ii<nn; ii++){
- Expr *pExpr;
- sCol[0].u.zToken = (char*)pIdx->azColl[ii];
- if( pIdx->aiColumn[ii]==XN_EXPR ){
- assert( pIdx->aColExpr!=0 );
- assert( pIdx->aColExpr->nExpr>ii );
- pExpr = pIdx->aColExpr->a[ii].pExpr;
- if( pExpr->op!=TK_COLLATE ){
- sCol[0].pLeft = pExpr;
+ nn = pIdx->nKeyCol;
+ for(ii=0; ii<nn; ii++){
+ Expr *pExpr;
+ sCol[0].u.zToken = (char*)pIdx->azColl[ii];
+ if( pIdx->aiColumn[ii]==XN_EXPR ){
+ assert( pIdx->aColExpr!=0 );
+ assert( pIdx->aColExpr->nExpr>ii );
+ pExpr = pIdx->aColExpr->a[ii].pExpr;
+ if( pExpr->op!=TK_COLLATE ){
+ sCol[0].pLeft = pExpr;
+ pExpr = &sCol[0];
+ }
+ }else{
+ sCol[0].pLeft = &sCol[1];
+ sCol[1].iColumn = pIdx->aiColumn[ii];
pExpr = &sCol[0];
}
- }else{
- sCol[0].pLeft = &sCol[1];
- sCol[1].iColumn = pIdx->aiColumn[ii];
- pExpr = &sCol[0];
- }
- for(jj=0; jj<nn; jj++){
- if( sqlite3ExprCompare(pParse, pTarget->a[jj].pExpr, pExpr,iCursor)<2 ){
- break; /* Column ii of the index matches column jj of target */
+ for(jj=0; jj<nn; jj++){
+ if( sqlite3ExprCompare(pParse,pTarget->a[jj].pExpr,pExpr,iCursor)<2 ){
+ break; /* Column ii of the index matches column jj of target */
+ }
+ }
+ if( jj>=nn ){
+ /* The target contains no match for column jj of the index */
+ break;
}
}
- if( jj>=nn ){
- /* The target contains no match for column jj of the index */
- break;
+ if( ii<nn ){
+ /* Column ii of the index did not match any term of the conflict target.
+ ** Continue the search with the next index. */
+ continue;
}
+ pUpsert->pUpsertIdx = pIdx;
+ break;
}
- if( ii<nn ){
- /* Column ii of the index did not match any term of the conflict target.
- ** Continue the search with the next index. */
- continue;
+ if( pUpsert->pUpsertIdx==0 ){
+ char zWhich[16];
+ if( nClause==0 && pUpsert->pNextUpsert==0 ){
+ zWhich[0] = 0;
+ }else{
+ sqlite3_snprintf(sizeof(zWhich),zWhich,"%r ", nClause+1);
+ }
+ sqlite3ErrorMsg(pParse, "%sON CONFLICT clause does not match any "
+ "PRIMARY KEY or UNIQUE constraint", zWhich);
+ return SQLITE_ERROR;
}
- pUpsert->pUpsertIdx = pIdx;
- return SQLITE_OK;
}
- sqlite3ErrorMsg(pParse, "ON CONFLICT clause does not match any "
- "PRIMARY KEY or UNIQUE constraint");
- return SQLITE_ERROR;
+ return SQLITE_OK;
+}
+
+/*
+** Return true if pUpsert is the last ON CONFLICT clause with a
+** conflict target, or if pUpsert is followed by another ON CONFLICT
+** clause that targets the INTEGER PRIMARY KEY.
+*/
+SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert *pUpsert){
+ Upsert *pNext;
+ if( NEVER(pUpsert==0) ) return 0;
+ pNext = pUpsert->pNextUpsert;
+ if( pNext==0 ) return 1;
+ if( pNext->pUpsertTarget==0 ) return 1;
+ if( pNext->pUpsertIdx==0 ) return 1;
+ return 0;
+}
+
+/*
+** Given the list of ON CONFLICT clauses described by pUpsert, and
+** a particular index pIdx, return a pointer to the particular ON CONFLICT
+** clause that applies to the index. Or, if the index is not subject to
+** any ON CONFLICT clause, return NULL.
+*/
+SQLITE_PRIVATE Upsert *sqlite3UpsertOfIndex(Upsert *pUpsert, Index *pIdx){
+ while(
+ pUpsert
+ && pUpsert->pUpsertTarget!=0
+ && pUpsert->pUpsertIdx!=pIdx
+ ){
+ pUpsert = pUpsert->pNextUpsert;
+ }
+ return pUpsert;
}
/*
@@ -139114,11 +142686,13 @@ SQLITE_PRIVATE void sqlite3UpsertDoUpdate(
SrcList *pSrc; /* FROM clause for the UPDATE */
int iDataCur;
int i;
+ Upsert *pTop = pUpsert;
assert( v!=0 );
assert( pUpsert!=0 );
- VdbeNoopComment((v, "Begin DO UPDATE of UPSERT"));
iDataCur = pUpsert->iDataCur;
+ pUpsert = sqlite3UpsertOfIndex(pTop, pIdx);
+ VdbeNoopComment((v, "Begin DO UPDATE of UPSERT"));
if( pIdx && iCur!=iDataCur ){
if( HasRowid(pTab) ){
int regRowid = sqlite3GetTempReg(pParse);
@@ -139148,19 +142722,17 @@ SQLITE_PRIVATE void sqlite3UpsertDoUpdate(
sqlite3VdbeJumpHere(v, i);
}
}
- /* pUpsert does not own pUpsertSrc - the outer INSERT statement does. So
- ** we have to make a copy before passing it down into sqlite3Update() */
- pSrc = sqlite3SrcListDup(db, pUpsert->pUpsertSrc, 0);
+ /* pUpsert does not own pTop->pUpsertSrc - the outer INSERT statement does.
+ ** So we have to make a copy before passing it down into sqlite3Update() */
+ pSrc = sqlite3SrcListDup(db, pTop->pUpsertSrc, 0);
/* excluded.* columns of type REAL need to be converted to a hard real */
for(i=0; i<pTab->nCol; i++){
if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){
- sqlite3VdbeAddOp1(v, OP_RealAffinity, pUpsert->regData+i);
+ sqlite3VdbeAddOp1(v, OP_RealAffinity, pTop->regData+i);
}
}
- sqlite3Update(pParse, pSrc, pUpsert->pUpsertSet,
- pUpsert->pUpsertWhere, OE_Abort, 0, 0, pUpsert);
- pUpsert->pUpsertSet = 0; /* Will have been deleted by sqlite3Update() */
- pUpsert->pUpsertWhere = 0; /* Will have been deleted by sqlite3Update() */
+ sqlite3Update(pParse, pSrc, sqlite3ExprListDup(db,pUpsert->pUpsertSet,0),
+ sqlite3ExprDup(db,pUpsert->pUpsertWhere,0), OE_Abort, 0, 0, pUpsert);
VdbeNoopComment((v, "End DO UPDATE of UPSERT"));
}
@@ -139509,8 +143081,8 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum(
BTREE_APPLICATION_ID, 0, /* Preserve the application id */
};
- assert( 1==sqlite3BtreeIsInTrans(pTemp) );
- assert( pOut!=0 || 1==sqlite3BtreeIsInTrans(pMain) );
+ assert( SQLITE_TXN_WRITE==sqlite3BtreeTxnState(pTemp) );
+ assert( pOut!=0 || SQLITE_TXN_WRITE==sqlite3BtreeTxnState(pMain) );
/* Copy Btree meta values */
for(i=0; i<ArraySize(aCopy); i+=2){
@@ -140066,7 +143638,7 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
sqlite3VdbeAddOp0(v, OP_Expire);
zWhere = sqlite3MPrintf(db, "name=%Q AND sql=%Q", pTab->zName, zStmt);
- sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere);
+ sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere, 0);
sqlite3DbFree(db, zStmt);
iReg = ++pParse->nMem;
@@ -140237,6 +143809,7 @@ static int vtabCallConstructor(
zType[i-1] = '\0';
}
pTab->aCol[iCol].colFlags |= COLFLAG_HIDDEN;
+ pTab->tabFlags |= TF_HasHidden;
oooHidden = TF_OOOHidden;
}else{
pTab->tabFlags |= oooHidden;
@@ -140405,7 +143978,7 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
Table *pNew = sParse.pNewTable;
Index *pIdx;
pTab->aCol = pNew->aCol;
- pTab->nCol = pNew->nCol;
+ pTab->nNVCol = pTab->nCol = pNew->nCol;
pTab->tabFlags |= pNew->tabFlags & (TF_WithoutRowid|TF_NoVisibleRowid);
pNew->nCol = 0;
pNew->aCol = 0;
@@ -140797,6 +144370,7 @@ SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
pTab->pSchema = db->aDb[0].pSchema;
assert( pTab->nModuleArg==0 );
pTab->iPKey = -1;
+ pTab->tabFlags |= TF_Eponymous;
addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
addModuleArgument(pParse, pTab, 0);
addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
@@ -140937,19 +144511,6 @@ SQLITE_API int sqlite3_vtab_config(sqlite3 *db, int op, ...){
#ifndef SQLITE_WHEREINT_H
#define SQLITE_WHEREINT_H
-/*
-** Trace output macros
-*/
-#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG)
-/***/ extern int sqlite3WhereTrace;
-#endif
-#if defined(SQLITE_DEBUG) \
- && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHERETRACE))
-# define WHERETRACE(K,X) if(sqlite3WhereTrace&(K)) sqlite3DebugPrintf X
-# define WHERETRACE_ENABLED 1
-#else
-# define WHERETRACE(K,X)
-#endif
/* Forward references
*/
@@ -141181,9 +144742,11 @@ struct WhereTerm {
u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */
int iParent; /* Disable pWC->a[iParent] when this term disabled */
int leftCursor; /* Cursor number of X in "X <op> <expr>" */
- int iField; /* Field in (?,?,?) IN (SELECT...) vector */
union {
- int leftColumn; /* Column number of X in "X <op> <expr>" */
+ struct {
+ int leftColumn; /* Column number of X in "X <op> <expr>" */
+ int iField; /* Field in (?,?,?) IN (SELECT...) vector */
+ } x; /* Opcode other than OP_OR or OP_AND */
WhereOrInfo *pOrInfo; /* Extra information if (eOperator & WO_OR)!=0 */
WhereAndInfo *pAndInfo; /* Extra information if (eOperator& WO_AND)!=0 */
} u;
@@ -141201,11 +144764,7 @@ struct WhereTerm {
#define TERM_ORINFO 0x0010 /* Need to free the WhereTerm.u.pOrInfo object */
#define TERM_ANDINFO 0x0020 /* Need to free the WhereTerm.u.pAndInfo obj */
#define TERM_OR_OK 0x0040 /* Used during OR-clause processing */
-#ifdef SQLITE_ENABLE_STAT4
-# define TERM_VNULL 0x0080 /* Manufactured x>NULL or x<=NULL term */
-#else
-# define TERM_VNULL 0x0000 /* Disabled if not using stat4 */
-#endif
+#define TERM_VNULL 0x0080 /* Manufactured x>NULL or x<=NULL term */
#define TERM_LIKEOPT 0x0100 /* Virtual terms from the LIKE optimization */
#define TERM_LIKECOND 0x0200 /* Conditionally this LIKE operator term */
#define TERM_LIKE 0x0400 /* The original LIKE operator */
@@ -141228,8 +144787,8 @@ struct WhereScan {
const char *zCollName; /* Required collating sequence, if not NULL */
Expr *pIdxExpr; /* Search for this index expression */
char idxaff; /* Must match this affinity, if zCollName!=NULL */
- unsigned char nEquiv; /* Number of entries in aEquiv[] */
- unsigned char iEquiv; /* Next unused slot in aEquiv[] */
+ unsigned char nEquiv; /* Number of entries in aiCur[] and aiColumn[] */
+ unsigned char iEquiv; /* Next unused slot in aiCur[] and aiColumn[] */
u32 opMask; /* Acceptable operators */
int k; /* Resume scanning at this->pWC->a[this->k] */
int aiCur[11]; /* Cursors in the equivalence class */
@@ -141408,6 +144967,7 @@ struct WhereInfo {
unsigned sorted :1; /* True if really sorted (not just grouped) */
LogEst nRowOut; /* Estimated number of output rows */
int iTop; /* The very beginning of the WHERE loop */
+ int iEndWhere; /* End of the WHERE clause itself */
WhereLoop *pLoops; /* List of all WhereLoop objects */
WhereExprMod *pExprMods; /* Expression modifications */
Bitmask revMask; /* Mask of ORDER BY terms that need reversing */
@@ -141474,7 +145034,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet*, Expr*);
SQLITE_PRIVATE Bitmask sqlite3WhereExprUsageNN(WhereMaskSet*, Expr*);
SQLITE_PRIVATE Bitmask sqlite3WhereExprListUsage(WhereMaskSet*, ExprList*);
SQLITE_PRIVATE void sqlite3WhereExprAnalyze(SrcList*, WhereClause*);
-SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereClause*);
+SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*);
@@ -141536,6 +145096,8 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereC
#define WHERE_PARTIALIDX 0x00020000 /* The automatic index is partial */
#define WHERE_IN_EARLYOUT 0x00040000 /* Perhaps quit IN loops early */
#define WHERE_BIGNULL_SORT 0x00080000 /* Column nEq of index is BIGNULL */
+#define WHERE_IN_SEEKSCAN 0x00100000 /* Seek-scan optimization for IN */
+#define WHERE_TRANSCONS 0x00200000 /* Uses a transitive constraint */
#endif /* !defined(SQLITE_WHEREINT_H) */
@@ -141651,7 +145213,7 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
if( sqlite3ParseToplevel(pParse)->explain==2 )
#endif
{
- struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom];
+ SrcItem *pItem = &pTabList->a[pLevel->iFrom];
Vdbe *v = pParse->pVdbe; /* VM being constructed */
sqlite3 *db = pParse->db; /* Database handle */
int isSearch; /* True for a SEARCH. False for SCAN. */
@@ -141670,16 +145232,8 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
|| (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX));
sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH);
- sqlite3_str_appendall(&str, isSearch ? "SEARCH" : "SCAN");
- if( pItem->pSelect ){
- sqlite3_str_appendf(&str, " SUBQUERY %u", pItem->pSelect->selId);
- }else{
- sqlite3_str_appendf(&str, " TABLE %s", pItem->zName);
- }
-
- if( pItem->zAlias ){
- sqlite3_str_appendf(&str, " AS %s", pItem->zAlias);
- }
+ str.printfFlags = SQLITE_PRINTF_INTERNAL;
+ sqlite3_str_appendf(&str, "%s %S", isSearch ? "SEARCH" : "SCAN", pItem);
if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){
const char *zFmt = 0;
Index *pIdx;
@@ -141827,6 +145381,12 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){
}else{
pTerm->wtFlags |= TERM_CODED;
}
+#ifdef WHERETRACE_ENABLED
+ if( sqlite3WhereTrace & 0x20000 ){
+ sqlite3DebugPrintf("DISABLE-");
+ sqlite3WhereTermPrint(pTerm, (int)(pTerm - (pTerm->pWC->a)));
+ }
+#endif
if( pTerm->iParent<0 ) break;
pTerm = &pTerm->pWC->a[pTerm->iParent];
assert( pTerm!=0 );
@@ -141949,7 +145509,7 @@ static Expr *removeUnindexableInClauseTerms(
for(i=iEq; i<pLoop->nLTerm; i++){
if( pLoop->aLTerm[i]->pExpr==pX ){
- int iField = pLoop->aLTerm[i]->iField - 1;
+ int iField = pLoop->aLTerm[i]->u.x.iField - 1;
if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */
pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr);
pOrigRhs->a[iField].pExpr = 0;
@@ -142092,6 +145652,9 @@ static int codeEqualityTerm(
if( pLevel->u.in.nIn==0 ){
pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse);
}
+ if( iEq>0 && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)==0 ){
+ pLoop->wsFlags |= WHERE_IN_EARLYOUT;
+ }
i = pLevel->u.in.nIn;
pLevel->u.in.nIn += nEq;
@@ -142118,7 +145681,6 @@ static int codeEqualityTerm(
if( iEq>0 ){
pIn->iBase = iReg - i;
pIn->nPrefix = i;
- pLoop->wsFlags |= WHERE_IN_EARLYOUT;
}else{
pIn->nPrefix = 0;
}
@@ -142128,13 +145690,36 @@ static int codeEqualityTerm(
pIn++;
}
}
+ testcase( iEq>0
+ && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)==0
+ && (pLoop->wsFlags & WHERE_VIRTUALTABLE)!=0 );
+ if( iEq>0
+ && (pLoop->wsFlags & (WHERE_IN_SEEKSCAN|WHERE_VIRTUALTABLE))==0
+ ){
+ sqlite3VdbeAddOp3(v, OP_SeekHit, pLevel->iIdxCur, 0, iEq);
+ }
}else{
pLevel->u.in.nIn = 0;
}
sqlite3DbFree(pParse->db, aiMap);
#endif
}
- disableTerm(pLevel, pTerm);
+
+ /* As an optimization, try to disable the WHERE clause term that is
+ ** driving the index as it will always be true. The correct answer is
+ ** obtained regardless, but we might get the answer with fewer CPU cycles
+ ** by omitting the term.
+ **
+ ** But do not disable the term unless we are certain that the term is
+ ** not a transitive constraint. For an example of where that does not
+ ** work, see https://sqlite.org/forum/forumpost/eb8613976a (2021-05-04)
+ */
+ if( (pLevel->pWLoop->wsFlags & WHERE_TRANSCONS)==0
+ || (pTerm->eOperator & WO_EQUIV)==0
+ ){
+ disableTerm(pLevel, pTerm);
+ }
+
return iReg;
}
@@ -142220,6 +145805,7 @@ static int codeAllEqualityTerms(
if( nSkip ){
int iIdxCur = pLevel->iIdxCur;
+ sqlite3VdbeAddOp3(v, OP_Null, 0, regBase, regBase+nSkip-1);
sqlite3VdbeAddOp1(v, (bRev?OP_Last:OP_Rewind), iIdxCur);
VdbeCoverageIf(v, bRev==0);
VdbeCoverageIf(v, bRev!=0);
@@ -142254,7 +145840,7 @@ static int codeAllEqualityTerms(
sqlite3ReleaseTempReg(pParse, regBase);
regBase = r1;
}else{
- sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j);
+ sqlite3VdbeAddOp2(v, OP_Copy, r1, regBase+j);
}
}
if( pTerm->eOperator & WO_IN ){
@@ -142271,7 +145857,7 @@ static int codeAllEqualityTerms(
sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk);
VdbeCoverage(v);
}
- if( zAff ){
+ if( pParse->db->mallocFailed==0 && pParse->nErr==0 ){
if( sqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_BLOB ){
zAff[j] = SQLITE_AFF_BLOB;
}
@@ -142434,7 +146020,7 @@ static int codeCursorHintFixExpr(Walker *pWalker, Expr *pExpr){
** Insert an OP_CursorHint instruction if it is appropriate to do so.
*/
static void codeCursorHint(
- struct SrcList_item *pTabItem, /* FROM clause item */
+ SrcItem *pTabItem, /* FROM clause item */
WhereInfo *pWInfo, /* The where clause */
WhereLevel *pLevel, /* Which loop to provide hints for */
WhereTerm *pEndRange /* Hint this end-of-scan boundary term if not NULL */
@@ -142620,7 +146206,7 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){
}
}
}else{
- assert( nReg==1 );
+ assert( nReg==1 || pParse->nErr );
sqlite3ExprCode(pParse, p, iReg);
}
}
@@ -142809,7 +146395,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
WhereClause *pWC; /* Decomposition of the entire WHERE clause */
WhereTerm *pTerm; /* A WHERE clause term */
sqlite3 *db; /* Database connection */
- struct SrcList_item *pTabItem; /* FROM clause term being coded */
+ SrcItem *pTabItem; /* FROM clause term being coded */
int addrBrk; /* Jump here to break out of the loop */
int addrHalt; /* addrBrk for the outermost loop */
int addrCont; /* Jump here to continue with next cycle */
@@ -142914,6 +146500,9 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
pLoop->u.vtab.needFree ? P4_DYNAMIC : P4_STATIC);
VdbeCoverage(v);
pLoop->u.vtab.needFree = 0;
+ /* An OOM inside of AddOp4(OP_VFilter) instruction above might have freed
+ ** the u.vtab.idxStr. NULL it out to prevent a use-after-free */
+ if( db->mallocFailed ) pLoop->u.vtab.idxStr = 0;
pLevel->p1 = iCur;
pLevel->op = pWInfo->eOnePass ? OP_Noop : OP_VNext;
pLevel->p2 = sqlite3VdbeCurrentAddr(v);
@@ -143172,6 +146761,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
u8 bStopAtNull = 0; /* Add condition to terminate at NULLs */
int omitTable; /* True if we use the index only */
int regBignull = 0; /* big-null flag register */
+ int addrSeekScan = 0; /* Opcode of the OP_SeekScan, if any */
pIdx = pLoop->u.btree.pIndex;
iIdxCur = pLevel->iIdxCur;
@@ -143243,14 +146833,18 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** a forward order scan on a descending index, interchange the
** start and end terms (pRangeStart and pRangeEnd).
*/
- if( (nEq<pIdx->nKeyCol && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC))
- || (bRev && pIdx->nKeyCol==nEq)
- ){
+ if( (nEq<pIdx->nColumn && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) ){
SWAP(WhereTerm *, pRangeEnd, pRangeStart);
SWAP(u8, bSeekPastNull, bStopAtNull);
SWAP(u8, nBtm, nTop);
}
+ if( iLevel>0 && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)!=0 ){
+ /* In case OP_SeekScan is used, ensure that the index cursor does not
+ ** point to a valid row for the first iteration of this loop. */
+ sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur);
+ }
+
/* Generate code to evaluate all constraint terms using == or IN
** and store the values of those terms in an array of registers
** starting at regBase.
@@ -143310,9 +146904,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** above has already left the cursor sitting on the correct row,
** so no further seeking is needed */
}else{
- if( pLoop->wsFlags & WHERE_IN_EARLYOUT ){
- sqlite3VdbeAddOp1(v, OP_SeekHit, iIdxCur);
- }
if( regBignull ){
sqlite3VdbeAddOp2(v, OP_Integer, 1, regBignull);
VdbeComment((v, "NULL-scan pass ctr"));
@@ -143320,6 +146911,20 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
op = aStartOp[(start_constraints<<2) + (startEq<<1) + bRev];
assert( op!=0 );
+ if( (pLoop->wsFlags & WHERE_IN_SEEKSCAN)!=0 && op==OP_SeekGE ){
+ assert( regBignull==0 );
+ /* TUNING: The OP_SeekScan opcode seeks to reduce the number
+ ** of expensive seek operations by replacing a single seek with
+ ** 1 or more step operations. The question is, how many steps
+ ** should we try before giving up and going with a seek. The cost
+ ** of a seek is proportional to the logarithm of the of the number
+ ** of entries in the tree, so basing the number of steps to try
+ ** on the estimated number of rows in the btree seems like a good
+ ** guess. */
+ addrSeekScan = sqlite3VdbeAddOp1(v, OP_SeekScan,
+ (pIdx->aiRowLogEst[0]+9)/10);
+ VdbeCoverage(v);
+ }
sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint);
VdbeCoverage(v);
VdbeCoverageIf(v, op==OP_Rewind); testcase( op==OP_Rewind );
@@ -143402,6 +147007,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
testcase( op==OP_IdxGE ); VdbeCoverageIf(v, op==OP_IdxGE );
testcase( op==OP_IdxLT ); VdbeCoverageIf(v, op==OP_IdxLT );
testcase( op==OP_IdxLE ); VdbeCoverageIf(v, op==OP_IdxLE );
+ if( addrSeekScan ) sqlite3VdbeJumpHere(v, addrSeekScan);
}
if( regBignull ){
/* During a NULL-scan, check to see if we have reached the end of
@@ -143421,8 +147027,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
testcase( op==OP_IdxLE ); VdbeCoverageIf(v, op==OP_IdxLE );
}
- if( pLoop->wsFlags & WHERE_IN_EARLYOUT ){
- sqlite3VdbeAddOp2(v, OP_SeekHit, iIdxCur, 1);
+ if( (pLoop->wsFlags & WHERE_IN_EARLYOUT)!=0 ){
+ sqlite3VdbeAddOp3(v, OP_SeekHit, iIdxCur, nEq, nEq);
}
/* Seek the table cursor, if required */
@@ -143431,17 +147037,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
if( omitTable ){
/* pIdx is a covering index. No need to access the main table. */
}else if( HasRowid(pIdx->pTable) ){
- if( (pWInfo->wctrlFlags & WHERE_SEEK_TABLE)
- || ( (pWInfo->wctrlFlags & WHERE_SEEK_UNIQ_TABLE)!=0
- && (pWInfo->eOnePass==ONEPASS_SINGLE || pLoop->nLTerm==0) )
- ){
- iRowidReg = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, iRowidReg);
- sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, iRowidReg);
- VdbeCoverage(v);
- }else{
- codeDeferredSeek(pWInfo, pIdx, iCur, iIdxCur);
- }
+ codeDeferredSeek(pWInfo, pIdx, iCur, iIdxCur);
}else if( iCur!=iIdxCur ){
Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
iRowidReg = sqlite3GetTempRange(pParse, pPk->nKeyCol);
@@ -143568,7 +147164,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
int iRetInit; /* Address of regReturn init */
int untestedTerms = 0; /* Some terms not completely tested */
int ii; /* Loop counter */
- u16 wctrlFlags; /* Flags for sub-WHERE clause */
Expr *pAndExpr = 0; /* An ".. AND (...)" expression */
Table *pTab = pTabItem->pTab;
@@ -143586,7 +147181,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
*/
if( pWInfo->nLevel>1 ){
int nNotReady; /* The number of notReady tables */
- struct SrcList_item *origSrc; /* Original list of tables */
+ SrcItem *origSrc; /* Original list of tables */
nNotReady = pWInfo->nLevel - iLevel - 1;
pOrTab = sqlite3StackAllocRaw(db,
sizeof(*pOrTab)+ nNotReady*sizeof(pOrTab->a[0]));
@@ -143659,7 +147254,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
/* The extra 0x10000 bit on the opcode is masked off and does not
** become part of the new Expr.op. However, it does make the
** op==TK_AND comparison inside of sqlite3PExpr() false, and this
- ** prevents sqlite3PExpr() from implementing AND short-circuit
+ ** prevents sqlite3PExpr() from applying the AND short-circuit
** optimization, which we do not want here. */
pAndExpr = sqlite3PExpr(pParse, TK_AND|0x10000, 0, pAndExpr);
}
@@ -143669,17 +147264,22 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** eliminating duplicates from other WHERE clauses, the action for each
** sub-WHERE clause is to to invoke the main loop body as a subroutine.
*/
- wctrlFlags = WHERE_OR_SUBCLAUSE | (pWInfo->wctrlFlags & WHERE_SEEK_TABLE);
ExplainQueryPlan((pParse, 1, "MULTI-INDEX OR"));
for(ii=0; ii<pOrWc->nTerm; ii++){
WhereTerm *pOrTerm = &pOrWc->a[ii];
if( pOrTerm->leftCursor==iCur || (pOrTerm->eOperator & WO_AND)!=0 ){
WhereInfo *pSubWInfo; /* Info for single OR-term scan */
Expr *pOrExpr = pOrTerm->pExpr; /* Current OR clause term */
+ Expr *pDelete; /* Local copy of OR clause term */
int jmp1 = 0; /* Address of jump operation */
testcase( (pTabItem[0].fg.jointype & JT_LEFT)!=0
&& !ExprHasProperty(pOrExpr, EP_FromJoin)
); /* See TH3 vtab25.400 and ticket 614b25314c766238 */
+ pDelete = pOrExpr = sqlite3ExprDup(db, pOrExpr, 0);
+ if( db->mallocFailed ){
+ sqlite3ExprDelete(db, pDelete);
+ continue;
+ }
if( pAndExpr ){
pAndExpr->pLeft = pOrExpr;
pOrExpr = pAndExpr;
@@ -143688,7 +147288,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
ExplainQueryPlan((pParse, 1, "INDEX %d", ii+1));
WHERETRACE(0xffff, ("Subplan for OR-clause:\n"));
pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0,
- wctrlFlags, iCovCur);
+ WHERE_OR_SUBCLAUSE, iCovCur);
assert( pSubWInfo || pParse->nErr || db->mallocFailed );
if( pSubWInfo ){
WhereLoop *pSubLoop;
@@ -143786,11 +147386,15 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
}else{
pCov = 0;
}
+ if( sqlite3WhereUsesDeferredSeek(pSubWInfo) ){
+ pWInfo->bDeferredSeek = 1;
+ }
/* Finish the loop through table entries that match term pOrTerm. */
sqlite3WhereEnd(pSubWInfo);
ExplainQueryPlanPop(pParse);
}
+ sqlite3ExprDelete(db, pDelete);
}
}
ExplainQueryPlanPop(pParse);
@@ -143938,7 +147542,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
#endif
assert( !ExprHasProperty(pE, EP_FromJoin) );
assert( (pTerm->prereqRight & pLevel->notReady)!=0 );
- pAlt = sqlite3WhereFindTerm(pWC, iCur, pTerm->u.leftColumn, notReady,
+ pAlt = sqlite3WhereFindTerm(pWC, iCur, pTerm->u.x.leftColumn, notReady,
WO_EQ|WO_IN|WO_IS, 0);
if( pAlt==0 ) continue;
if( pAlt->wtFlags & (TERM_CODED) ) continue;
@@ -143955,6 +147559,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
sEAlt = *pAlt->pExpr;
sEAlt.pLeft = pE->pLeft;
sqlite3ExprIfFalse(pParse, &sEAlt, addrCont, SQLITE_JUMPIFNULL);
+ pAlt->wtFlags |= TERM_CODED;
}
/* For a LEFT OUTER JOIN, generate code that will record the fact that
@@ -144507,6 +148112,7 @@ static void whereCombineDisjuncts(
int op; /* Operator for the combined expression */
int idxNew; /* Index in pWC of the next virtual term */
+ if( (pOne->wtFlags | pTwo->wtFlags) & TERM_VNULL ) return;
if( (pOne->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return;
if( (pTwo->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return;
if( (eOp & (WO_EQ|WO_LT|WO_LE))!=eOp
@@ -144794,7 +148400,7 @@ static void exprAnalyzeOrTerm(
assert( pOrTerm->wtFlags & (TERM_COPIED|TERM_VIRTUAL) );
continue;
}
- iColumn = pOrTerm->u.leftColumn;
+ iColumn = pOrTerm->u.x.leftColumn;
iCursor = pOrTerm->leftCursor;
pLeft = pOrTerm->pExpr->pLeft;
break;
@@ -144816,7 +148422,7 @@ static void exprAnalyzeOrTerm(
assert( pOrTerm->eOperator & WO_EQ );
if( pOrTerm->leftCursor!=iCursor ){
pOrTerm->wtFlags &= ~TERM_OR_OK;
- }else if( pOrTerm->u.leftColumn!=iColumn || (iColumn==XN_EXPR
+ }else if( pOrTerm->u.x.leftColumn!=iColumn || (iColumn==XN_EXPR
&& sqlite3ExprCompare(pParse, pOrTerm->pExpr->pLeft, pLeft, -1)
)){
okToChngToIN = 0;
@@ -144851,7 +148457,7 @@ static void exprAnalyzeOrTerm(
if( (pOrTerm->wtFlags & TERM_OR_OK)==0 ) continue;
assert( pOrTerm->eOperator & WO_EQ );
assert( pOrTerm->leftCursor==iCursor );
- assert( pOrTerm->u.leftColumn==iColumn );
+ assert( pOrTerm->u.x.leftColumn==iColumn );
pDup = sqlite3ExprDup(db, pOrTerm->pExpr->pRight, 0);
pList = sqlite3ExprListAppend(pWInfo->pParse, pList, pDup);
pLeft = pOrTerm->pExpr->pLeft;
@@ -144867,7 +148473,7 @@ static void exprAnalyzeOrTerm(
idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC);
testcase( idxNew==0 );
exprAnalyze(pSrc, pWC, idxNew);
- /* pTerm = &pWC->a[idxTerm]; // would be needed if pTerm where used again */
+ /* pTerm = &pWC->a[idxTerm]; // would be needed if pTerm where reused */
markTermAsChild(pWC, idxNew, idxTerm);
}else{
sqlite3ExprListDelete(db, pList);
@@ -144991,6 +148597,7 @@ static int exprMightBeIndexed(
assert( op<=TK_GE );
if( pExpr->op==TK_VECTOR && (op>=TK_GT && ALWAYS(op<=TK_GE)) ){
pExpr = pExpr->x.pList->a[0].pExpr;
+
}
if( pExpr->op==TK_COLUMN ){
@@ -145003,6 +148610,7 @@ static int exprMightBeIndexed(
return exprMightBeIndexed2(pFrom,mPrereq,aiCurCol,pExpr);
}
+
/*
** The input to this routine is an WhereTerm structure with only the
** "pExpr" field filled in. The job of this routine is to analyze the
@@ -145087,25 +148695,26 @@ static void exprAnalyze(
Expr *pRight = sqlite3ExprSkipCollate(pExpr->pRight);
u16 opMask = (pTerm->prereqRight & prereqLeft)==0 ? WO_ALL : WO_EQUIV;
- if( pTerm->iField>0 ){
+ if( pTerm->u.x.iField>0 ){
assert( op==TK_IN );
assert( pLeft->op==TK_VECTOR );
- pLeft = pLeft->x.pList->a[pTerm->iField-1].pExpr;
+ pLeft = pLeft->x.pList->a[pTerm->u.x.iField-1].pExpr;
}
if( exprMightBeIndexed(pSrc, prereqLeft, aiCurCol, pLeft, op) ){
pTerm->leftCursor = aiCurCol[0];
- pTerm->u.leftColumn = aiCurCol[1];
+ pTerm->u.x.leftColumn = aiCurCol[1];
pTerm->eOperator = operatorMask(op) & opMask;
}
if( op==TK_IS ) pTerm->wtFlags |= TERM_IS;
if( pRight
&& exprMightBeIndexed(pSrc, pTerm->prereqRight, aiCurCol, pRight, op)
+ && !ExprHasProperty(pRight, EP_FixedCol)
){
WhereTerm *pNew;
Expr *pDup;
u16 eExtraOp = 0; /* Extra bits for pNew->eOperator */
- assert( pTerm->iField==0 );
+ assert( pTerm->u.x.iField==0 );
if( pTerm->leftCursor>=0 ){
int idxNew;
pDup = sqlite3ExprDup(db, pExpr, 0);
@@ -145131,11 +148740,17 @@ static void exprAnalyze(
}
pNew->wtFlags |= exprCommute(pParse, pDup);
pNew->leftCursor = aiCurCol[0];
- pNew->u.leftColumn = aiCurCol[1];
+ pNew->u.x.leftColumn = aiCurCol[1];
testcase( (prereqLeft | extraRight) != prereqLeft );
pNew->prereqRight = prereqLeft | extraRight;
pNew->prereqAll = prereqAll;
pNew->eOperator = (operatorMask(pDup->op) + eExtraOp) & opMask;
+ }else if( op==TK_ISNULL && 0==sqlite3ExprCanBeNull(pLeft) ){
+ pExpr->op = TK_TRUEFALSE;
+ pExpr->u.zToken = "false";
+ ExprSetProperty(pExpr, EP_IsFalse);
+ pTerm->prereqAll = 0;
+ pTerm->eOperator = 0;
}
}
@@ -145187,6 +148802,42 @@ static void exprAnalyze(
pTerm = &pWC->a[idxTerm];
}
#endif /* SQLITE_OMIT_OR_OPTIMIZATION */
+ /* The form "x IS NOT NULL" can sometimes be evaluated more efficiently
+ ** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a
+ ** virtual term of that form.
+ **
+ ** The virtual term must be tagged with TERM_VNULL.
+ */
+ else if( pExpr->op==TK_NOTNULL ){
+ if( pExpr->pLeft->op==TK_COLUMN
+ && pExpr->pLeft->iColumn>=0
+ && !ExprHasProperty(pExpr, EP_FromJoin)
+ ){
+ Expr *pNewExpr;
+ Expr *pLeft = pExpr->pLeft;
+ int idxNew;
+ WhereTerm *pNewTerm;
+
+ pNewExpr = sqlite3PExpr(pParse, TK_GT,
+ sqlite3ExprDup(db, pLeft, 0),
+ sqlite3ExprAlloc(db, TK_NULL, 0, 0));
+
+ idxNew = whereClauseInsert(pWC, pNewExpr,
+ TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL);
+ if( idxNew ){
+ pNewTerm = &pWC->a[idxNew];
+ pNewTerm->prereqRight = 0;
+ pNewTerm->leftCursor = pLeft->iTable;
+ pNewTerm->u.x.leftColumn = pLeft->iColumn;
+ pNewTerm->eOperator = WO_GT;
+ markTermAsChild(pWC, idxNew, idxTerm);
+ pTerm = &pWC->a[idxTerm];
+ pTerm->wtFlags |= TERM_COPIED;
+ pNewTerm->prereqAll = pTerm->prereqAll;
+ }
+ }
+ }
+
#ifndef SQLITE_OMIT_LIKE_OPTIMIZATION
/* Add constraints to reduce the search space on a LIKE or GLOB
@@ -145202,7 +148853,8 @@ static void exprAnalyze(
** bound is made all lowercase so that the bounds also work when comparing
** BLOBs.
*/
- if( pWC->op==TK_AND
+ else if( pExpr->op==TK_FUNCTION
+ && pWC->op==TK_AND
&& isLikeOrGlob(pParse, pExpr, &pStr1, &isComplete, &noCase)
){
Expr *pLeft; /* LHS of LIKE/GLOB operator */
@@ -145272,52 +148924,6 @@ static void exprAnalyze(
}
#endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- /* Add a WO_AUX auxiliary term to the constraint set if the
- ** current expression is of the form "column OP expr" where OP
- ** is an operator that gets passed into virtual tables but which is
- ** not normally optimized for ordinary tables. In other words, OP
- ** is one of MATCH, LIKE, GLOB, REGEXP, !=, IS, IS NOT, or NOT NULL.
- ** This information is used by the xBestIndex methods of
- ** virtual tables. The native query optimizer does not attempt
- ** to do anything with MATCH functions.
- */
- if( pWC->op==TK_AND ){
- Expr *pRight = 0, *pLeft = 0;
- int res = isAuxiliaryVtabOperator(db, pExpr, &eOp2, &pLeft, &pRight);
- while( res-- > 0 ){
- int idxNew;
- WhereTerm *pNewTerm;
- Bitmask prereqColumn, prereqExpr;
-
- prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight);
- prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft);
- if( (prereqExpr & prereqColumn)==0 ){
- Expr *pNewExpr;
- pNewExpr = sqlite3PExpr(pParse, TK_MATCH,
- 0, sqlite3ExprDup(db, pRight, 0));
- if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){
- ExprSetProperty(pNewExpr, EP_FromJoin);
- pNewExpr->iRightJoinTable = pExpr->iRightJoinTable;
- }
- idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
- testcase( idxNew==0 );
- pNewTerm = &pWC->a[idxNew];
- pNewTerm->prereqRight = prereqExpr;
- pNewTerm->leftCursor = pLeft->iTable;
- pNewTerm->u.leftColumn = pLeft->iColumn;
- pNewTerm->eOperator = WO_AUX;
- pNewTerm->eMatchOp = eOp2;
- markTermAsChild(pWC, idxNew, idxTerm);
- pTerm = &pWC->a[idxTerm];
- pTerm->wtFlags |= TERM_COPIED;
- pNewTerm->prereqAll = pTerm->prereqAll;
- }
- SWAP(Expr*, pLeft, pRight);
- }
- }
-#endif /* SQLITE_OMIT_VIRTUALTABLE */
-
/* If there is a vector == or IS term - e.g. "(a, b) == (?, ?)" - create
** new terms for each component comparison - "a = ?" and "b = ?". The
** new terms completely replace the original vector comparison, which is
@@ -145325,12 +148931,12 @@ static void exprAnalyze(
**
** This is only required if at least one side of the comparison operation
** is not a sub-select. */
- if( pWC->op==TK_AND
- && (pExpr->op==TK_EQ || pExpr->op==TK_IS)
- && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1
- && sqlite3ExprVectorSize(pExpr->pRight)==nLeft
- && ( (pExpr->pLeft->flags & EP_xIsSelect)==0
- || (pExpr->pRight->flags & EP_xIsSelect)==0)
+ if( (pExpr->op==TK_EQ || pExpr->op==TK_IS)
+ && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1
+ && sqlite3ExprVectorSize(pExpr->pRight)==nLeft
+ && ( (pExpr->pLeft->flags & EP_xIsSelect)==0
+ || (pExpr->pRight->flags & EP_xIsSelect)==0)
+ && pWC->op==TK_AND
){
int i;
for(i=0; i<nLeft; i++){
@@ -145352,67 +148958,76 @@ static void exprAnalyze(
/* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create
** a virtual term for each vector component. The expression object
** used by each such virtual term is pExpr (the full vector IN(...)
- ** expression). The WhereTerm.iField variable identifies the index within
+ ** expression). The WhereTerm.u.x.iField variable identifies the index within
** the vector on the LHS that the virtual term represents.
**
** This only works if the RHS is a simple SELECT (not a compound) that does
** not use window functions.
*/
- if( pWC->op==TK_AND && pExpr->op==TK_IN && pTerm->iField==0
+ else if( pExpr->op==TK_IN
+ && pTerm->u.x.iField==0
&& pExpr->pLeft->op==TK_VECTOR
&& pExpr->x.pSelect->pPrior==0
#ifndef SQLITE_OMIT_WINDOWFUNC
&& pExpr->x.pSelect->pWin==0
#endif
+ && pWC->op==TK_AND
){
int i;
for(i=0; i<sqlite3ExprVectorSize(pExpr->pLeft); i++){
int idxNew;
idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL);
- pWC->a[idxNew].iField = i+1;
+ pWC->a[idxNew].u.x.iField = i+1;
exprAnalyze(pSrc, pWC, idxNew);
markTermAsChild(pWC, idxNew, idxTerm);
}
}
-#ifdef SQLITE_ENABLE_STAT4
- /* When sqlite_stat4 histogram data is available an operator of the
- ** form "x IS NOT NULL" can sometimes be evaluated more efficiently
- ** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a
- ** virtual term of that form.
- **
- ** Note that the virtual term must be tagged with TERM_VNULL.
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ /* Add a WO_AUX auxiliary term to the constraint set if the
+ ** current expression is of the form "column OP expr" where OP
+ ** is an operator that gets passed into virtual tables but which is
+ ** not normally optimized for ordinary tables. In other words, OP
+ ** is one of MATCH, LIKE, GLOB, REGEXP, !=, IS, IS NOT, or NOT NULL.
+ ** This information is used by the xBestIndex methods of
+ ** virtual tables. The native query optimizer does not attempt
+ ** to do anything with MATCH functions.
*/
- if( pExpr->op==TK_NOTNULL
- && pExpr->pLeft->op==TK_COLUMN
- && pExpr->pLeft->iColumn>=0
- && !ExprHasProperty(pExpr, EP_FromJoin)
- && OptimizationEnabled(db, SQLITE_Stat4)
- ){
- Expr *pNewExpr;
- Expr *pLeft = pExpr->pLeft;
- int idxNew;
- WhereTerm *pNewTerm;
-
- pNewExpr = sqlite3PExpr(pParse, TK_GT,
- sqlite3ExprDup(db, pLeft, 0),
- sqlite3ExprAlloc(db, TK_NULL, 0, 0));
-
- idxNew = whereClauseInsert(pWC, pNewExpr,
- TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL);
- if( idxNew ){
- pNewTerm = &pWC->a[idxNew];
- pNewTerm->prereqRight = 0;
- pNewTerm->leftCursor = pLeft->iTable;
- pNewTerm->u.leftColumn = pLeft->iColumn;
- pNewTerm->eOperator = WO_GT;
- markTermAsChild(pWC, idxNew, idxTerm);
- pTerm = &pWC->a[idxTerm];
- pTerm->wtFlags |= TERM_COPIED;
- pNewTerm->prereqAll = pTerm->prereqAll;
+ else if( pWC->op==TK_AND ){
+ Expr *pRight = 0, *pLeft = 0;
+ int res = isAuxiliaryVtabOperator(db, pExpr, &eOp2, &pLeft, &pRight);
+ while( res-- > 0 ){
+ int idxNew;
+ WhereTerm *pNewTerm;
+ Bitmask prereqColumn, prereqExpr;
+
+ prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight);
+ prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft);
+ if( (prereqExpr & prereqColumn)==0 ){
+ Expr *pNewExpr;
+ pNewExpr = sqlite3PExpr(pParse, TK_MATCH,
+ 0, sqlite3ExprDup(db, pRight, 0));
+ if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){
+ ExprSetProperty(pNewExpr, EP_FromJoin);
+ pNewExpr->iRightJoinTable = pExpr->iRightJoinTable;
+ }
+ idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
+ testcase( idxNew==0 );
+ pNewTerm = &pWC->a[idxNew];
+ pNewTerm->prereqRight = prereqExpr;
+ pNewTerm->leftCursor = pLeft->iTable;
+ pNewTerm->u.x.leftColumn = pLeft->iColumn;
+ pNewTerm->eOperator = WO_AUX;
+ pNewTerm->eMatchOp = eOp2;
+ markTermAsChild(pWC, idxNew, idxTerm);
+ pTerm = &pWC->a[idxTerm];
+ pTerm->wtFlags |= TERM_COPIED;
+ pNewTerm->prereqAll = pTerm->prereqAll;
+ }
+ SWAP(Expr*, pLeft, pRight);
}
}
-#endif /* SQLITE_ENABLE_STAT4 */
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
/* Prevent ON clause terms of a LEFT JOIN from being used to drive
** an index for tables to the left of the join.
@@ -145447,6 +149062,7 @@ static void exprAnalyze(
SQLITE_PRIVATE void sqlite3WhereSplit(WhereClause *pWC, Expr *pExpr, u8 op){
Expr *pE2 = sqlite3ExprSkipCollateAndLikely(pExpr);
pWC->op = op;
+ assert( pE2!=0 || pExpr==0 );
if( pE2==0 ) return;
if( pE2->op!=op ){
whereClauseInsert(pWC, pExpr, 0);
@@ -145571,7 +149187,7 @@ SQLITE_PRIVATE void sqlite3WhereExprAnalyze(
*/
SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(
Parse *pParse, /* Parsing context */
- struct SrcList_item *pItem, /* The FROM clause term to process */
+ SrcItem *pItem, /* The FROM clause term to process */
WhereClause *pWC /* Xfer function arguments to here */
){
Table *pTab;
@@ -145648,12 +149264,6 @@ struct HiddenIndexInfo {
/* Forward declaration of methods */
static int whereLoopResize(sqlite3*, WhereLoop*, int);
-/* Test variable that can be set to enable WHERE tracing */
-#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG)
-/***/ int sqlite3WhereTrace = 0;
-#endif
-
-
/*
** Return the estimated number of output rows from a WHERE clause
*/
@@ -145717,6 +149327,32 @@ SQLITE_PRIVATE int sqlite3WhereOrderByLimitOptLabel(WhereInfo *pWInfo){
}
/*
+** While generating code for the min/max optimization, after handling
+** the aggregate-step call to min() or max(), check to see if any
+** additional looping is required. If the output order is such that
+** we are certain that the correct answer has already been found, then
+** code an OP_Goto to by pass subsequent processing.
+**
+** Any extra OP_Goto that is coded here is an optimization. The
+** correct answer should be obtained regardless. This OP_Goto just
+** makes the answer appear faster.
+*/
+SQLITE_PRIVATE void sqlite3WhereMinMaxOptEarlyOut(Vdbe *v, WhereInfo *pWInfo){
+ WhereLevel *pInner;
+ int i;
+ if( !pWInfo->bOrderedInnerLoop ) return;
+ if( pWInfo->nOBSat==0 ) return;
+ for(i=pWInfo->nLevel-1; i>=0; i--){
+ pInner = &pWInfo->a[i];
+ if( (pInner->pWLoop->wsFlags & WHERE_COLUMN_IN)!=0 ){
+ sqlite3VdbeGoto(v, pInner->addrNxt);
+ return;
+ }
+ }
+ sqlite3VdbeGoto(v, pWInfo->iBreak);
+}
+
+/*
** Return the VDBE address or label to jump to in order to continue
** immediately with the next row of a WHERE clause.
*/
@@ -145847,6 +149483,18 @@ static void createMask(WhereMaskSet *pMaskSet, int iCursor){
}
/*
+** If the right-hand branch of the expression is a TK_COLUMN, then return
+** a pointer to the right-hand branch. Otherwise, return NULL.
+*/
+static Expr *whereRightSubexprIsColumn(Expr *p){
+ p = sqlite3ExprSkipCollateAndLikely(p->pRight);
+ if( ALWAYS(p!=0) && p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){
+ return p;
+ }
+ return 0;
+}
+
+/*
** Advance to the next WhereTerm that matches according to the criteria
** established when the pScan object was initialized by whereScanInit().
** Return NULL if there are no more matching WhereTerms.
@@ -145868,7 +149516,7 @@ static WhereTerm *whereScanNext(WhereScan *pScan){
do{
for(pTerm=pWC->a+k; k<pWC->nTerm; k++, pTerm++){
if( pTerm->leftCursor==iCur
- && pTerm->u.leftColumn==iColumn
+ && pTerm->u.x.leftColumn==iColumn
&& (iColumn!=XN_EXPR
|| sqlite3ExprCompareSkip(pTerm->pExpr->pLeft,
pScan->pIdxExpr,iCur)==0)
@@ -145876,8 +149524,7 @@ static WhereTerm *whereScanNext(WhereScan *pScan){
){
if( (pTerm->eOperator & WO_EQUIV)!=0
&& pScan->nEquiv<ArraySize(pScan->aiCur)
- && (pX = sqlite3ExprSkipCollateAndLikely(pTerm->pExpr->pRight))->op
- ==TK_COLUMN
+ && (pX = whereRightSubexprIsColumn(pTerm->pExpr))!=0
){
int j;
for(j=0; j<pScan->nEquiv; j++){
@@ -145918,6 +149565,18 @@ static WhereTerm *whereScanNext(WhereScan *pScan){
}
pScan->pWC = pWC;
pScan->k = k+1;
+#ifdef WHERETRACE_ENABLED
+ if( sqlite3WhereTrace & 0x20000 ){
+ int ii;
+ sqlite3DebugPrintf("SCAN-TERM %p: nEquiv=%d",
+ pTerm, pScan->nEquiv);
+ for(ii=0; ii<pScan->nEquiv; ii++){
+ sqlite3DebugPrintf(" {%d:%d}",
+ pScan->aiCur[ii], pScan->aiColumn[ii]);
+ }
+ sqlite3DebugPrintf("\n");
+ }
+#endif
return pTerm;
}
}
@@ -146073,7 +149732,8 @@ static int findIndexCol(
for(i=0; i<pList->nExpr; i++){
Expr *p = sqlite3ExprSkipCollateAndLikely(pList->a[i].pExpr);
- if( p->op==TK_COLUMN
+ if( ALWAYS(p!=0)
+ && (p->op==TK_COLUMN || p->op==TK_AGG_COLUMN)
&& p->iColumn==pIdx->aiColumn[iCol]
&& p->iTable==iBase
){
@@ -146137,7 +149797,9 @@ static int isDistinctRedundant(
*/
for(i=0; i<pDistinct->nExpr; i++){
Expr *p = sqlite3ExprSkipCollateAndLikely(pDistinct->a[i].pExpr);
- if( p->op==TK_COLUMN && p->iTable==iBase && p->iColumn<0 ) return 1;
+ if( NEVER(p==0) ) continue;
+ if( p->op!=TK_COLUMN && p->op!=TK_AGG_COLUMN ) continue;
+ if( p->iTable==iBase && p->iColumn<0 ) return 1;
}
/* Loop through all indices on the table, checking each to see if it makes
@@ -146155,6 +149817,7 @@ static int isDistinctRedundant(
*/
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
if( !IsUniqueIndex(pIdx) ) continue;
+ if( pIdx->pPartIdxWhere ) continue;
for(i=0; i<pIdx->nKeyCol; i++){
if( 0==sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask)0, WO_EQ, pIdx) ){
if( findIndexCol(pParse, pDistinct, iBase, pIdx, i)<0 ) break;
@@ -146209,14 +149872,14 @@ static void translateColumnToCopy(
pOp->p2 = pOp->p3;
pOp->p3 = 0;
}else if( pOp->opcode==OP_Rowid ){
- if( iAutoidxCur ){
- pOp->opcode = OP_Sequence;
- pOp->p1 = iAutoidxCur;
- }else{
+ pOp->opcode = OP_Sequence;
+ pOp->p1 = iAutoidxCur;
+#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
+ if( iAutoidxCur==0 ){
pOp->opcode = OP_Null;
- pOp->p1 = 0;
pOp->p3 = 0;
}
+#endif
}
}
}
@@ -146274,7 +149937,7 @@ static void whereTraceIndexInfoOutputs(sqlite3_index_info *p){
*/
static int termCanDriveIndex(
WhereTerm *pTerm, /* WHERE clause term to check */
- struct SrcList_item *pSrc, /* Table we are trying to access */
+ SrcItem *pSrc, /* Table we are trying to access */
Bitmask notReady /* Tables in outer loops of the join */
){
char aff;
@@ -146290,8 +149953,8 @@ static int termCanDriveIndex(
return 0;
}
if( (pTerm->prereqRight & notReady)!=0 ) return 0;
- if( pTerm->u.leftColumn<0 ) return 0;
- aff = pSrc->pTab->aCol[pTerm->u.leftColumn].affinity;
+ if( pTerm->u.x.leftColumn<0 ) return 0;
+ aff = pSrc->pTab->aCol[pTerm->u.x.leftColumn].affinity;
if( !sqlite3IndexAffinityOk(pTerm->pExpr, aff) ) return 0;
testcase( pTerm->pExpr->op==TK_IS );
return 1;
@@ -146308,7 +149971,7 @@ static int termCanDriveIndex(
static void constructAutomaticIndex(
Parse *pParse, /* The parsing context */
WhereClause *pWC, /* The WHERE clause */
- struct SrcList_item *pSrc, /* The FROM clause term to get the next index */
+ SrcItem *pSrc, /* The FROM clause term to get the next index */
Bitmask notReady, /* Mask of cursors that are not available */
WhereLevel *pLevel /* Write new index here */
){
@@ -146332,7 +149995,7 @@ static void constructAutomaticIndex(
u8 sentWarning = 0; /* True if a warnning has been issued */
Expr *pPartial = 0; /* Partial Index Expression */
int iContinue = 0; /* Jump here to skip excluded rows */
- struct SrcList_item *pTabItem; /* FROM clause term being indexed */
+ SrcItem *pTabItem; /* FROM clause term being indexed */
int addrCounter = 0; /* Address where integer counter is initialized */
int regBase; /* Array of registers where record is assembled */
@@ -146362,7 +150025,7 @@ static void constructAutomaticIndex(
sqlite3ExprDup(pParse->db, pExpr, 0));
}
if( termCanDriveIndex(pTerm, pSrc, notReady) ){
- int iCol = pTerm->u.leftColumn;
+ int iCol = pTerm->u.x.leftColumn;
Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol);
testcase( iCol==BMS );
testcase( iCol==BMS-1 );
@@ -146381,7 +150044,7 @@ static void constructAutomaticIndex(
}
}
}
- assert( nKeyCol>0 );
+ assert( nKeyCol>0 || pParse->db->mallocFailed );
pLoop->u.btree.nEq = pLoop->nLTerm = nKeyCol;
pLoop->wsFlags = WHERE_COLUMN_EQ | WHERE_IDX_ONLY | WHERE_INDEXED
| WHERE_AUTO_INDEX;
@@ -146415,14 +150078,14 @@ static void constructAutomaticIndex(
idxCols = 0;
for(pTerm=pWC->a; pTerm<pWCEnd; pTerm++){
if( termCanDriveIndex(pTerm, pSrc, notReady) ){
- int iCol = pTerm->u.leftColumn;
+ int iCol = pTerm->u.x.leftColumn;
Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol);
testcase( iCol==BMS-1 );
testcase( iCol==BMS );
if( (idxCols & cMask)==0 ){
Expr *pX = pTerm->pExpr;
idxCols |= cMask;
- pIdx->aiColumn[n] = pTerm->u.leftColumn;
+ pIdx->aiColumn[n] = pTerm->u.x.leftColumn;
pColl = sqlite3ExprCompareCollSeq(pParse, pX);
assert( pColl!=0 || pParse->nErr>0 ); /* TH3 collate01.800 */
pIdx->azColl[n] = pColl ? pColl->zName : sqlite3StrBINARY;
@@ -146516,7 +150179,7 @@ static sqlite3_index_info *allocateIndexInfo(
Parse *pParse, /* The parsing context */
WhereClause *pWC, /* The WHERE clause being analyzed */
Bitmask mUnusable, /* Ignore terms with these prereqs */
- struct SrcList_item *pSrc, /* The FROM clause term that is the vtab */
+ SrcItem *pSrc, /* The FROM clause term that is the vtab */
ExprList *pOrderBy, /* The ORDER BY clause */
u16 *pmNoOmit /* Mask of terms not to omit */
){
@@ -146543,7 +150206,7 @@ static sqlite3_index_info *allocateIndexInfo(
testcase( pTerm->eOperator & WO_ALL );
if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue;
if( pTerm->wtFlags & TERM_VNULL ) continue;
- assert( pTerm->u.leftColumn>=(-1) );
+ assert( pTerm->u.x.leftColumn>=(-1) );
nTerm++;
}
@@ -146603,8 +150266,8 @@ static sqlite3_index_info *allocateIndexInfo(
){
continue;
}
- assert( pTerm->u.leftColumn>=(-1) );
- pIdxCons[j].iColumn = pTerm->u.leftColumn;
+ assert( pTerm->u.x.leftColumn>=(-1) );
+ pIdxCons[j].iColumn = pTerm->u.x.leftColumn;
pIdxCons[j].iTermOffset = i;
op = pTerm->eOperator & WO_ALL;
if( op==WO_IN ) op = WO_EQ;
@@ -147367,9 +151030,9 @@ SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){
if( pTerm->wtFlags & TERM_CODED ) zType[3] = 'C';
if( pTerm->eOperator & WO_SINGLE ){
sqlite3_snprintf(sizeof(zLeft),zLeft,"left={%d:%d}",
- pTerm->leftCursor, pTerm->u.leftColumn);
+ pTerm->leftCursor, pTerm->u.x.leftColumn);
}else if( (pTerm->eOperator & WO_OR)!=0 && pTerm->u.pOrInfo!=0 ){
- sqlite3_snprintf(sizeof(zLeft),zLeft,"indexable=0x%lld",
+ sqlite3_snprintf(sizeof(zLeft),zLeft,"indexable=0x%llx",
pTerm->u.pOrInfo->indexable);
}else{
sqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor);
@@ -147383,8 +151046,8 @@ SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){
sqlite3DebugPrintf(" prob=%-3d prereq=%llx,%llx",
pTerm->truthProb, (u64)pTerm->prereqAll, (u64)pTerm->prereqRight);
}
- if( pTerm->iField ){
- sqlite3DebugPrintf(" iField=%d", pTerm->iField);
+ if( pTerm->u.x.iField ){
+ sqlite3DebugPrintf(" iField=%d", pTerm->u.x.iField);
}
if( pTerm->iParent>=0 ){
sqlite3DebugPrintf(" iParent=%d", pTerm->iParent);
@@ -147414,7 +151077,7 @@ SQLITE_PRIVATE void sqlite3WhereClausePrint(WhereClause *pWC){
SQLITE_PRIVATE void sqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC){
WhereInfo *pWInfo = pWC->pWInfo;
int nb = 1+(pWInfo->pTabList->nSrc+3)/4;
- struct SrcList_item *pItem = pWInfo->pTabList->a + p->iTab;
+ SrcItem *pItem = pWInfo->pTabList->a + p->iTab;
Table *pTab = pItem->pTab;
Bitmask mAll = (((Bitmask)1)<<(nb*4)) - 1;
sqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId,
@@ -147518,7 +151181,7 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){
static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){
whereLoopClearUnion(db, pTo);
if( whereLoopResize(db, pTo, pFrom->nLTerm) ){
- memset(&pTo->u, 0, sizeof(pTo->u));
+ memset(pTo, 0, WHERE_LOOP_XFER_SZ);
return SQLITE_NOMEM_BKPT;
}
memcpy(pTo, pFrom, WHERE_LOOP_XFER_SZ);
@@ -147561,6 +151224,17 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){
sqlite3DbFreeNN(db, pWInfo);
}
+/* Undo all Expr node modifications
+*/
+static void whereUndoExprMods(WhereInfo *pWInfo){
+ while( pWInfo->pExprMods ){
+ WhereExprMod *p = pWInfo->pExprMods;
+ pWInfo->pExprMods = p->pNext;
+ memcpy(p->pExpr, &p->orig, sizeof(p->orig));
+ sqlite3DbFree(pWInfo->pParse->db, p);
+ }
+}
+
/*
** Return TRUE if all of the following are true:
**
@@ -148025,7 +151699,7 @@ static int whereRangeVectorLen(
*/
static int whereLoopAddBtreeIndex(
WhereLoopBuilder *pBuilder, /* The WhereLoop factory */
- struct SrcList_item *pSrc, /* FROM clause term being analyzed */
+ SrcItem *pSrc, /* FROM clause term being analyzed */
Index *pProbe, /* An index on pSrc */
LogEst nInMul /* log(Number of iterations due to IN) */
){
@@ -148051,9 +151725,9 @@ static int whereLoopAddBtreeIndex(
pNew = pBuilder->pNew;
if( db->mallocFailed ) return SQLITE_NOMEM_BKPT;
- WHERETRACE(0x800, ("BEGIN %s.addBtreeIdx(%s), nEq=%d, nSkip=%d\n",
+ WHERETRACE(0x800, ("BEGIN %s.addBtreeIdx(%s), nEq=%d, nSkip=%d, rRun=%d\n",
pProbe->pTable->zName,pProbe->zName,
- pNew->u.btree.nEq, pNew->nSkip));
+ pNew->u.btree.nEq, pNew->nSkip, pNew->rRun));
assert( (pNew->wsFlags & WHERE_VIRTUALTABLE)==0 );
assert( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 );
@@ -148066,6 +151740,8 @@ static int whereLoopAddBtreeIndex(
if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE);
assert( pNew->u.btree.nEq<pProbe->nColumn );
+ assert( pNew->u.btree.nEq<pProbe->nKeyCol
+ || pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY );
saved_nEq = pNew->u.btree.nEq;
saved_nBtm = pNew->u.btree.nBtm;
@@ -148147,8 +151823,8 @@ static int whereLoopAddBtreeIndex(
/* "x IN (value, value, ...)" */
nIn = sqlite3LogEst(pExpr->x.pList->nExpr);
}
- if( pProbe->hasStat1 ){
- LogEst M, logK, safetyMargin;
+ if( pProbe->hasStat1 && rLogSize>=10 ){
+ LogEst M, logK, x;
/* Let:
** N = the total number of rows in the table
** K = the number of entries on the RHS of the IN operator
@@ -148166,20 +151842,30 @@ static int whereLoopAddBtreeIndex(
** a safety margin of 2 (LogEst: 10) that favors using the IN operator
** with the index, as using an index has better worst-case behavior.
** If we do not have real sqlite_stat1 data, always prefer to use
- ** the index.
+ ** the index. Do not bother with this optimization on very small
+ ** tables (less than 2 rows) as it is pointless in that case.
*/
M = pProbe->aiRowLogEst[saved_nEq];
logK = estLog(nIn);
- safetyMargin = 10; /* TUNING: extra weight for indexed IN */
- if( M + logK + safetyMargin < nIn + rLogSize ){
+ /* TUNING v----- 10 to bias toward indexed IN */
+ x = M + logK + 10 - (nIn + rLogSize);
+ if( x>=0 ){
WHERETRACE(0x40,
- ("Scan preferred over IN operator on column %d of \"%s\" (%d<%d)\n",
- saved_nEq, pProbe->zName, M+logK+10, nIn+rLogSize));
- continue;
+ ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d) "
+ "prefers indexed lookup\n",
+ saved_nEq, M, logK, nIn, rLogSize, x));
+ }else if( nInMul<2 && OptimizationEnabled(db, SQLITE_SeekScan) ){
+ WHERETRACE(0x40,
+ ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d"
+ " nInMul=%d) prefers skip-scan\n",
+ saved_nEq, M, logK, nIn, rLogSize, x, nInMul));
+ pNew->wsFlags |= WHERE_IN_SEEKSCAN;
}else{
WHERETRACE(0x40,
- ("IN operator preferred on column %d of \"%s\" (%d>=%d)\n",
- saved_nEq, pProbe->zName, M+logK+10, nIn+rLogSize));
+ ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d"
+ " nInMul=%d) prefers normal scan\n",
+ saved_nEq, M, logK, nIn, rLogSize, x, nInMul));
+ continue;
}
}
pNew->wsFlags |= WHERE_COLUMN_IN;
@@ -148198,6 +151884,7 @@ static int whereLoopAddBtreeIndex(
pNew->wsFlags |= WHERE_UNQ_WANTED;
}
}
+ if( scan.iEquiv>1 ) pNew->wsFlags |= WHERE_TRANSCONS;
}else if( eOp & WO_ISNULL ){
pNew->wsFlags |= WHERE_COLUMN_NULL;
}else if( eOp & (WO_GT|WO_GE) ){
@@ -148210,7 +151897,7 @@ static int whereLoopAddBtreeIndex(
pBtm = pTerm;
pTop = 0;
if( pTerm->wtFlags & TERM_LIKEOPT ){
- /* Range contraints that come from the LIKE optimization are
+ /* Range constraints that come from the LIKE optimization are
** always used in pairs. */
pTop = &pTerm[1];
assert( (pTop-(pTerm->pWC->a))<pTerm->pWC->nTerm );
@@ -148259,7 +151946,7 @@ static int whereLoopAddBtreeIndex(
tRowcnt nOut = 0;
if( nInMul==0
&& pProbe->nSample
- && pNew->u.btree.nEq<=pProbe->nSampleCol
+ && ALWAYS(pNew->u.btree.nEq<=pProbe->nSampleCol)
&& ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect))
&& OptimizationEnabled(db, SQLITE_Stat4)
){
@@ -148341,6 +152028,8 @@ static int whereLoopAddBtreeIndex(
if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
&& pNew->u.btree.nEq<pProbe->nColumn
+ && (pNew->u.btree.nEq<pProbe->nKeyCol ||
+ pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY)
){
whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn);
}
@@ -148421,6 +152110,7 @@ static int indexMightHelpWithOrderBy(
if( (pOB = pBuilder->pWInfo->pOrderBy)==0 ) return 0;
for(ii=0; ii<pOB->nExpr; ii++){
Expr *pExpr = sqlite3ExprSkipCollateAndLikely(pOB->a[ii].pExpr);
+ if( NEVER(pExpr==0) ) continue;
if( pExpr->op==TK_COLUMN && pExpr->iTable==iCursor ){
if( pExpr->iColumn<0 ) return 1;
for(jj=0; jj<pIndex->nKeyCol; jj++){
@@ -148461,6 +152151,7 @@ static int whereUsablePartialIndex(
if( (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->iRightJoinTable==iTab)
&& (isLeft==0 || ExprHasProperty(pExpr, EP_FromJoin))
&& sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab)
+ && (pTerm->wtFlags & TERM_VNULL)==0
){
return 1;
}
@@ -148514,7 +152205,7 @@ static int whereLoopAddBtree(
LogEst aiRowEstPk[2]; /* The aiRowLogEst[] value for the sPk index */
i16 aiColumnPk = -1; /* The aColumn[] value for the sPk index */
SrcList *pTabList; /* The FROM clause */
- struct SrcList_item *pSrc; /* The FROM clause btree term to add */
+ SrcItem *pSrc; /* The FROM clause btree term to add */
WhereLoop *pNew; /* Template WhereLoop object */
int rc = SQLITE_OK; /* Return code */
int iSortIdx = 1; /* Index number */
@@ -148532,9 +152223,9 @@ static int whereLoopAddBtree(
pWC = pBuilder->pWC;
assert( !IsVirtual(pSrc->pTab) );
- if( pSrc->pIBIndex ){
+ if( pSrc->fg.isIndexedBy ){
/* An INDEXED BY clause specifies a particular index to use */
- pProbe = pSrc->pIBIndex;
+ pProbe = pSrc->u2.pIBIndex;
}else if( !HasRowid(pTab) ){
pProbe = pTab->pIndex;
}else{
@@ -148570,7 +152261,7 @@ static int whereLoopAddBtree(
if( !pBuilder->pOrSet /* Not part of an OR optimization */
&& (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0
&& (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0
- && pSrc->pIBIndex==0 /* Has no INDEXED BY clause */
+ && !pSrc->fg.isIndexedBy /* Has no INDEXED BY clause */
&& !pSrc->fg.notIndexed /* Has no NOT INDEXED clause */
&& HasRowid(pTab) /* Not WITHOUT ROWID table. (FIXME: Why not?) */
&& !pSrc->fg.isCorrelated /* Not a correlated subquery */
@@ -148620,7 +152311,7 @@ static int whereLoopAddBtree(
/* Loop over all indices. If there was an INDEXED BY clause, then only
** consider index pProbe. */
for(; rc==SQLITE_OK && pProbe;
- pProbe=(pSrc->pIBIndex ? 0 : pProbe->pNext), iSortIdx++
+ pProbe=(pSrc->fg.isIndexedBy ? 0 : pProbe->pNext), iSortIdx++
){
int isLeft = (pSrc->fg.jointype & JT_OUTER)!=0;
if( pProbe->pPartIdxWhere!=0
@@ -148652,8 +152343,23 @@ static int whereLoopAddBtree(
/* Full table scan */
pNew->iSortIdx = b ? iSortIdx : 0;
- /* TUNING: Cost of full table scan is (N*3.0). */
+ /* TUNING: Cost of full table scan is 3.0*N. The 3.0 factor is an
+ ** extra cost designed to discourage the use of full table scans,
+ ** since index lookups have better worst-case performance if our
+ ** stat guesses are wrong. Reduce the 3.0 penalty slightly
+ ** (to 2.75) if we have valid STAT4 information for the table.
+ ** At 2.75, a full table scan is preferred over using an index on
+ ** a column with just two distinct values where each value has about
+ ** an equal number of appearances. Without STAT4 data, we still want
+ ** to use an index in that case, since the constraint might be for
+ ** the scarcer of the two values, and in that case an index lookup is
+ ** better.
+ */
+#ifdef SQLITE_ENABLE_STAT4
+ pNew->rRun = rSize + 16 - 2*((pTab->tabFlags & TF_HasStat4)!=0);
+#else
pNew->rRun = rSize + 16;
+#endif
ApplyCostMultiplier(pNew->rRun, pTab->costMult);
whereLoopOutputAdjust(pWC, pNew, rSize);
rc = whereLoopInsert(pBuilder, pNew);
@@ -148780,7 +152486,7 @@ static int whereLoopAddVirtualOne(
int rc = SQLITE_OK;
WhereLoop *pNew = pBuilder->pNew;
Parse *pParse = pBuilder->pWInfo->pParse;
- struct SrcList_item *pSrc = &pBuilder->pWInfo->pTabList->a[pNew->iTab];
+ SrcItem *pSrc = &pBuilder->pWInfo->pTabList->a[pNew->iTab];
int nConstraint = pIdxInfo->nConstraint;
assert( (mUsable & mPrereq)==mPrereq );
@@ -148972,7 +152678,7 @@ static int whereLoopAddVirtual(
WhereInfo *pWInfo; /* WHERE analysis context */
Parse *pParse; /* The parsing context */
WhereClause *pWC; /* The WHERE clause */
- struct SrcList_item *pSrc; /* The FROM clause term to search */
+ SrcItem *pSrc; /* The FROM clause term to search */
sqlite3_index_info *p; /* Object to pass to xBestIndex() */
int nConstraint; /* Number of constraints in p */
int bIn; /* True if plan uses IN(...) operator */
@@ -149100,7 +152806,7 @@ static int whereLoopAddOr(
WhereClause tempWC;
WhereLoopBuilder sSubBuild;
WhereOrSet sSum, sCur;
- struct SrcList_item *pItem;
+ SrcItem *pItem;
pWC = pBuilder->pWC;
pWCEnd = pWC->a + pWC->nTerm;
@@ -149156,7 +152862,9 @@ static int whereLoopAddOr(
if( rc==SQLITE_OK ){
rc = whereLoopAddOr(&sSubBuild, mPrereq, mUnusable);
}
- assert( rc==SQLITE_OK || rc==SQLITE_DONE || sCur.n==0 );
+ assert( rc==SQLITE_OK || rc==SQLITE_DONE || sCur.n==0
+ || rc==SQLITE_NOMEM );
+ testcase( rc==SQLITE_NOMEM && sCur.n>0 );
testcase( rc==SQLITE_DONE );
if( sCur.n==0 ){
sSum.n = 0;
@@ -149216,8 +152924,8 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){
Bitmask mPrior = 0;
int iTab;
SrcList *pTabList = pWInfo->pTabList;
- struct SrcList_item *pItem;
- struct SrcList_item *pEnd = &pTabList->a[pWInfo->nLevel];
+ SrcItem *pItem;
+ SrcItem *pEnd = &pTabList->a[pWInfo->nLevel];
sqlite3 *db = pWInfo->pParse->db;
int rc = SQLITE_OK;
WhereLoop *pNew;
@@ -149240,7 +152948,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pItem->pTab) ){
- struct SrcList_item *p;
+ SrcItem *p;
for(p=&pItem[1]; p<pEnd; p++){
if( mUnusable || (p->fg.jointype & (JT_LEFT|JT_CROSS)) ){
mUnusable |= sqlite3WhereGetMask(&pWInfo->sMaskSet, p->iCursor);
@@ -149384,7 +153092,8 @@ static i8 wherePathSatisfiesOrderBy(
for(i=0; i<nOrderBy; i++){
if( MASKBIT(i) & obSat ) continue;
pOBExpr = sqlite3ExprSkipCollateAndLikely(pOrderBy->a[i].pExpr);
- if( pOBExpr->op!=TK_COLUMN ) continue;
+ if( NEVER(pOBExpr==0) ) continue;
+ if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue;
if( pOBExpr->iTable!=iCur ) continue;
pTerm = sqlite3WhereFindTerm(&pWInfo->sWC, iCur, pOBExpr->iColumn,
~ready, eqOpMask, 0);
@@ -149424,6 +153133,10 @@ static i8 wherePathSatisfiesOrderBy(
assert( nColumn==nKeyCol+1 || !HasRowid(pIndex->pTable) );
assert( pIndex->aiColumn[nColumn-1]==XN_ROWID
|| !HasRowid(pIndex->pTable));
+ /* All relevant terms of the index must also be non-NULL in order
+ ** for isOrderDistinct to be true. So the isOrderDistint value
+ ** computed here might be a false positive. Corrections will be
+ ** made at tag-20210426-1 below */
isOrderDistinct = IsUniqueIndex(pIndex)
&& (pLoop->wsFlags & WHERE_SKIPSCAN)==0;
}
@@ -149491,14 +153204,18 @@ static i8 wherePathSatisfiesOrderBy(
}
/* An unconstrained column that might be NULL means that this
- ** WhereLoop is not well-ordered
+ ** WhereLoop is not well-ordered. tag-20210426-1
*/
- if( isOrderDistinct
- && iColumn>=0
- && j>=pLoop->u.btree.nEq
- && pIndex->pTable->aCol[iColumn].notNull==0
- ){
- isOrderDistinct = 0;
+ if( isOrderDistinct ){
+ if( iColumn>=0
+ && j>=pLoop->u.btree.nEq
+ && pIndex->pTable->aCol[iColumn].notNull==0
+ ){
+ isOrderDistinct = 0;
+ }
+ if( iColumn==XN_EXPR ){
+ isOrderDistinct = 0;
+ }
}
/* Find the ORDER BY term that corresponds to the j-th column
@@ -149510,9 +153227,10 @@ static i8 wherePathSatisfiesOrderBy(
pOBExpr = sqlite3ExprSkipCollateAndLikely(pOrderBy->a[i].pExpr);
testcase( wctrlFlags & WHERE_GROUPBY );
testcase( wctrlFlags & WHERE_DISTINCTBY );
+ if( NEVER(pOBExpr==0) ) continue;
if( (wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY))==0 ) bOnce = 0;
if( iColumn>=XN_ROWID ){
- if( pOBExpr->op!=TK_COLUMN ) continue;
+ if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue;
if( pOBExpr->iTable!=iCur ) continue;
if( pOBExpr->iColumn!=iColumn ) continue;
}else{
@@ -149664,16 +153382,24 @@ static LogEst whereSortingCost(
** cost = (3.0 * N * log(N)) * (Y/X)
**
** The (Y/X) term is implemented using stack variable rScale
- ** below. */
+ ** below.
+ */
LogEst rScale, rSortCost;
assert( nOrderBy>0 && 66==sqlite3LogEst(100) );
rScale = sqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66;
rSortCost = nRow + rScale + 16;
/* Multiple by log(M) where M is the number of output rows.
- ** Use the LIMIT for M if it is smaller */
+ ** Use the LIMIT for M if it is smaller. Or if this sort is for
+ ** a DISTINCT operator, M will be the number of distinct output
+ ** rows, so fudge it downwards a bit.
+ */
if( (pWInfo->wctrlFlags & WHERE_USE_LIMIT)!=0 && pWInfo->iLimit<nRow ){
nRow = pWInfo->iLimit;
+ }else if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT) ){
+ /* TUNING: In the sort for a DISTINCT operator, assume that the DISTINCT
+ ** reduces the number of output rows by a factor of 2 */
+ if( nRow>10 ){ nRow -= 10; assert( 10==sqlite3LogEst(2) ); }
}
rSortCost += estLog(nRow);
return rSortCost;
@@ -150085,7 +153811,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
*/
static int whereShortCut(WhereLoopBuilder *pBuilder){
WhereInfo *pWInfo;
- struct SrcList_item *pItem;
+ SrcItem *pItem;
WhereClause *pWC;
WhereTerm *pTerm;
WhereLoop *pLoop;
@@ -150544,7 +154270,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){
pWInfo->revMask = ALLBITS;
}
- if( pParse->nErr || NEVER(db->mallocFailed) ){
+ if( pParse->nErr || db->mallocFailed ){
goto whereBeginError;
}
#ifdef WHERETRACE_ENABLED
@@ -150605,7 +154331,8 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
*/
notReady = ~(Bitmask)0;
if( pWInfo->nLevel>=2
- && pResultSet!=0 /* guarantees condition (1) above */
+ && pResultSet!=0 /* these two combine to guarantee */
+ && 0==(wctrlFlags & WHERE_AGG_DISTINCT) /* condition (1) above */
&& OptimizationEnabled(db, SQLITE_OmitNoopJoin)
){
int i;
@@ -150615,7 +154342,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
}
for(i=pWInfo->nLevel-1; i>=1; i--){
WhereTerm *pTerm, *pEnd;
- struct SrcList_item *pItem;
+ SrcItem *pItem;
pLoop = pWInfo->a[i].pWLoop;
pItem = &pWInfo->pTabList->a[pLoop->iTab];
if( (pItem->fg.jointype & JT_LEFT)==0 ) continue;
@@ -150705,7 +154432,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
for(ii=0, pLevel=pWInfo->a; ii<nTabList; ii++, pLevel++){
Table *pTab; /* Table to open */
int iDb; /* Index of database containing table/index */
- struct SrcList_item *pTabItem;
+ SrcItem *pTabItem;
pTabItem = &pTabList->a[pLevel->iFrom];
pTab = pTabItem->pTab;
@@ -150800,6 +154527,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
if( (pLoop->wsFlags & WHERE_CONSTRAINT)!=0
&& (pLoop->wsFlags & (WHERE_COLUMN_RANGE|WHERE_SKIPSCAN))==0
&& (pLoop->wsFlags & WHERE_BIGNULL_SORT)==0
+ && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)==0
&& (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0
&& pWInfo->eDistinct!=WHERE_DISTINCT_ORDERED
){
@@ -150857,11 +154585,14 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
/* Done. */
VdbeModuleComment((v, "Begin WHERE-core"));
+ pWInfo->iEndWhere = sqlite3VdbeCurrentAddr(v);
return pWInfo;
/* Jump here if malloc fails */
whereBeginError:
if( pWInfo ){
+ testcase( pWInfo->pExprMods!=0 );
+ whereUndoExprMods(pWInfo);
pParse->nQueryLoop = pWInfo->savedNQueryLoop;
whereInfoFree(db, pWInfo);
}
@@ -150900,6 +154631,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
WhereLoop *pLoop;
SrcList *pTabList = pWInfo->pTabList;
sqlite3 *db = pParse->db;
+ int iEnd = sqlite3VdbeCurrentAddr(v);
/* Generate loop termination code.
*/
@@ -150957,10 +154689,14 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
int j;
sqlite3VdbeResolveLabel(v, pLevel->addrNxt);
for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){
+ assert( sqlite3VdbeGetOp(v, pIn->addrInTop+1)->opcode==OP_IsNull
+ || pParse->db->mallocFailed );
sqlite3VdbeJumpHere(v, pIn->addrInTop+1);
if( pIn->eEndLoopOp!=OP_Noop ){
if( pIn->nPrefix ){
- assert( pLoop->wsFlags & WHERE_IN_EARLYOUT );
+ int bEarlyOut =
+ (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0
+ && (pLoop->wsFlags & WHERE_IN_EARLYOUT)!=0;
if( pLevel->iLeftJoin ){
/* For LEFT JOIN queries, cursor pIn->iCur may not have been
** opened yet. This occurs for WHERE clauses such as
@@ -150971,16 +154707,19 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
** jump over the OP_Next or OP_Prev instruction about to
** be coded. */
sqlite3VdbeAddOp2(v, OP_IfNotOpen, pIn->iCur,
- sqlite3VdbeCurrentAddr(v) + 2 +
- ((pLoop->wsFlags & WHERE_VIRTUALTABLE)==0)
- );
+ sqlite3VdbeCurrentAddr(v) + 2 + bEarlyOut);
VdbeCoverage(v);
}
- if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ){
+ if( bEarlyOut ){
sqlite3VdbeAddOp4Int(v, OP_IfNoHope, pLevel->iIdxCur,
sqlite3VdbeCurrentAddr(v)+2,
pIn->iBase, pIn->nPrefix);
VdbeCoverage(v);
+ /* Retarget the OP_IsNull against the left operand of IN so
+ ** it jumps past the OP_IfNoHope. This is because the
+ ** OP_IsNull also bypasses the OP_Affinity opcode that is
+ ** required by OP_IfNoHope. */
+ sqlite3VdbeJumpHere(v, pIn->addrInTop+1);
}
}
sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop);
@@ -151037,9 +154776,9 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
assert( pWInfo->nLevel<=pTabList->nSrc );
for(i=0, pLevel=pWInfo->a; i<pWInfo->nLevel; i++, pLevel++){
int k, last;
- VdbeOp *pOp;
+ VdbeOp *pOp, *pLastOp;
Index *pIdx = 0;
- struct SrcList_item *pTabItem = &pTabList->a[pLevel->iFrom];
+ SrcItem *pTabItem = &pTabList->a[pLevel->iFrom];
Table *pTab = pTabItem->pTab;
assert( pTab!=0 );
pLoop = pLevel->pWLoop;
@@ -151095,20 +154834,31 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
pIdx = pLevel->u.pCovidx;
}
if( pIdx
- && (pWInfo->eOnePass==ONEPASS_OFF || !HasRowid(pIdx->pTable))
&& !db->mallocFailed
){
- last = sqlite3VdbeCurrentAddr(v);
- k = pLevel->addrBody;
+ if( pWInfo->eOnePass==ONEPASS_OFF || !HasRowid(pIdx->pTable) ){
+ last = iEnd;
+ }else{
+ last = pWInfo->iEndWhere;
+ }
+ k = pLevel->addrBody + 1;
#ifdef SQLITE_DEBUG
if( db->flags & SQLITE_VdbeAddopTrace ){
printf("TRANSLATE opcodes in range %d..%d\n", k, last-1);
}
+ /* Proof that the "+1" on the k value above is safe */
+ pOp = sqlite3VdbeGetOp(v, k - 1);
+ assert( pOp->opcode!=OP_Column || pOp->p1!=pLevel->iTabCur );
+ assert( pOp->opcode!=OP_Rowid || pOp->p1!=pLevel->iTabCur );
+ assert( pOp->opcode!=OP_IfNullRow || pOp->p1!=pLevel->iTabCur );
#endif
pOp = sqlite3VdbeGetOp(v, k);
- for(; k<last; k++, pOp++){
- if( pOp->p1!=pLevel->iTabCur ) continue;
- if( pOp->opcode==OP_Column
+ pLastOp = pOp + (last - k);
+ assert( pOp<=pLastOp );
+ do{
+ if( pOp->p1!=pLevel->iTabCur ){
+ /* no-op */
+ }else if( pOp->opcode==OP_Column
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
|| pOp->opcode==OP_Offset
#endif
@@ -151139,23 +154889,19 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
pOp->p1 = pLevel->iIdxCur;
OpcodeRewriteTrace(db, k, pOp);
}
- }
+#ifdef SQLITE_DEBUG
+ k++;
+#endif
+ }while( (++pOp)<pLastOp );
#ifdef SQLITE_DEBUG
if( db->flags & SQLITE_VdbeAddopTrace ) printf("TRANSLATE complete\n");
#endif
}
}
- /* Undo all Expr node modifications */
- while( pWInfo->pExprMods ){
- WhereExprMod *p = pWInfo->pExprMods;
- pWInfo->pExprMods = p->pNext;
- memcpy(p->pExpr, &p->orig, sizeof(p->orig));
- sqlite3DbFree(db, p);
- }
-
/* Final cleanup
*/
+ if( pWInfo->pExprMods ) whereUndoExprMods(pWInfo);
pParse->nQueryLoop = pWInfo->savedNQueryLoop;
whereInfoFree(db, pWInfo);
return;
@@ -151953,6 +155699,7 @@ static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){
case TK_AGG_FUNCTION:
case TK_COLUMN: {
int iCol = -1;
+ if( pParse->db->mallocFailed ) return WRC_Abort;
if( p->pSub ){
int i;
for(i=0; i<p->pSub->nExpr; i++){
@@ -152062,9 +155809,14 @@ static ExprList *exprListAppendList(
int i;
int nInit = pList ? pList->nExpr : 0;
for(i=0; i<pAppend->nExpr; i++){
- Expr *pDup = sqlite3ExprDup(pParse->db, pAppend->a[i].pExpr, 0);
+ sqlite3 *db = pParse->db;
+ Expr *pDup = sqlite3ExprDup(db, pAppend->a[i].pExpr, 0);
assert( pDup==0 || !ExprHasProperty(pDup, EP_MemToken) );
- if( bIntToNull && pDup ){
+ if( db->mallocFailed ){
+ sqlite3ExprDelete(db, pDup);
+ break;
+ }
+ if( bIntToNull ){
int iDummy;
Expr *pSub;
for(pSub=pDup; ExprHasProperty(pSub, EP_Skip); pSub=pSub->pLeft){
@@ -152100,6 +155852,14 @@ static int sqlite3WindowExtraAggFuncDepth(Walker *pWalker, Expr *pExpr){
return WRC_Continue;
}
+static int disallowAggregatesInOrderByCb(Walker *pWalker, Expr *pExpr){
+ if( pExpr->op==TK_AGG_FUNCTION && pExpr->pAggInfo==0 ){
+ sqlite3ErrorMsg(pWalker->pParse,
+ "misuse of aggregate: %s()", pExpr->u.zToken);
+ }
+ return WRC_Continue;
+}
+
/*
** If the SELECT statement passed as the second argument does not invoke
** any SQL window functions, this function is a no-op. Otherwise, it
@@ -152109,7 +155869,7 @@ static int sqlite3WindowExtraAggFuncDepth(Walker *pWalker, Expr *pExpr){
*/
SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){
int rc = SQLITE_OK;
- if( p->pWin && p->pPrior==0 && (p->selFlags & SF_WinRewrite)==0 ){
+ if( p->pWin && p->pPrior==0 && ALWAYS((p->selFlags & SF_WinRewrite)==0) ){
Vdbe *v = sqlite3GetVdbe(pParse);
sqlite3 *db = pParse->db;
Select *pSub = 0; /* The subquery */
@@ -152133,6 +155893,11 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){
}
sqlite3AggInfoPersistWalkerInit(&w, pParse);
sqlite3WalkSelect(&w, p);
+ if( (p->selFlags & SF_Aggregate)==0 ){
+ w.xExprCallback = disallowAggregatesInOrderByCb;
+ w.xSelectCallback = 0;
+ sqlite3WalkExprList(&w, p->pOrderBy);
+ }
p->pSrc = 0;
p->pWhere = 0;
@@ -152469,15 +156234,19 @@ SQLITE_PRIVATE void sqlite3WindowAttach(Parse *pParse, Expr *p, Window *pWin){
** SELECT, or (b) the windows already linked use a compatible window frame.
*/
SQLITE_PRIVATE void sqlite3WindowLink(Select *pSel, Window *pWin){
- if( pSel!=0
- && (0==pSel->pWin || 0==sqlite3WindowCompare(0, pSel->pWin, pWin, 0))
- ){
- pWin->pNextWin = pSel->pWin;
- if( pSel->pWin ){
- pSel->pWin->ppThis = &pWin->pNextWin;
+ if( pSel ){
+ if( 0==pSel->pWin || 0==sqlite3WindowCompare(0, pSel->pWin, pWin, 0) ){
+ pWin->pNextWin = pSel->pWin;
+ if( pSel->pWin ){
+ pSel->pWin->ppThis = &pWin->pNextWin;
+ }
+ pSel->pWin = pWin;
+ pWin->ppThis = &pSel->pWin;
+ }else{
+ if( sqlite3ExprListCompare(pWin->pPartition, pSel->pWin->pPartition,-1) ){
+ pSel->selFlags |= SF_MultiPart;
+ }
}
- pSel->pWin = pWin;
- pWin->ppThis = &pSel->pWin;
}
}
@@ -152630,6 +156399,7 @@ static void windowCheckValue(Parse *pParse, int reg, int eCond){
VdbeCoverageIf(v, eCond==2);
}
sqlite3VdbeAddOp3(v, aOp[eCond], regZero, sqlite3VdbeCurrentAddr(v)+2, reg);
+ sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC);
VdbeCoverageNeverNullIf(v, eCond==0); /* NULL case captured by */
VdbeCoverageNeverNullIf(v, eCond==1); /* the OP_MustBeInt */
VdbeCoverageNeverNullIf(v, eCond==2);
@@ -152724,6 +156494,7 @@ struct WindowCodeArg {
int regGosub; /* Register used with OP_Gosub(addrGosub) */
int regArg; /* First in array of accumulator registers */
int eDelete; /* See above */
+ int regRowid;
WindowCsrAndReg start;
WindowCsrAndReg current;
@@ -152840,15 +156611,15 @@ static void windowAggStep(
}
if( pWin->bExprArgs ){
- int iStart = sqlite3VdbeCurrentAddr(v);
- VdbeOp *pOp, *pEnd;
+ int iOp = sqlite3VdbeCurrentAddr(v);
+ int iEnd;
nArg = pWin->pOwner->x.pList->nExpr;
regArg = sqlite3GetTempRange(pParse, nArg);
sqlite3ExprCodeExprList(pParse, pWin->pOwner->x.pList, regArg, 0, 0);
- pEnd = sqlite3VdbeGetOp(v, -1);
- for(pOp=sqlite3VdbeGetOp(v, iStart); pOp<=pEnd; pOp++){
+ for(iEnd=sqlite3VdbeCurrentAddr(v); iOp<iEnd; iOp++){
+ VdbeOp *pOp = sqlite3VdbeGetOp(v, iOp);
if( pOp->opcode==OP_Column && pOp->p1==pWin->iEphCsr ){
pOp->p1 = csr;
}
@@ -153207,7 +156978,7 @@ static void windowIfNewPeer(
** if( csr1.peerVal - regVal <= csr2.peerVal ) goto lbl;
**
** A special type of arithmetic is used such that if csr1.peerVal is not
-** a numeric type (real or integer), then the result of the addition addition
+** a numeric type (real or integer), then the result of the addition
** or subtraction is a a copy of csr1.peerVal.
*/
static void windowCodeRangeTest(
@@ -153226,6 +156997,12 @@ static void windowCodeRangeTest(
int regString = ++pParse->nMem; /* Reg. for constant value '' */
int arith = OP_Add; /* OP_Add or OP_Subtract */
int addrGe; /* Jump destination */
+ int addrDone = sqlite3VdbeMakeLabel(pParse); /* Address past OP_Ge */
+ CollSeq *pColl;
+
+ /* Read the peer-value from each cursor into a register */
+ windowReadPeerValues(p, csr1, reg1);
+ windowReadPeerValues(p, csr2, reg2);
assert( op==OP_Ge || op==OP_Gt || op==OP_Le );
assert( pOrderBy && pOrderBy->nExpr==1 );
@@ -153238,34 +157015,11 @@ static void windowCodeRangeTest(
arith = OP_Subtract;
}
- /* Read the peer-value from each cursor into a register */
- windowReadPeerValues(p, csr1, reg1);
- windowReadPeerValues(p, csr2, reg2);
-
VdbeModuleComment((v, "CodeRangeTest: if( R%d %s R%d %s R%d ) goto lbl",
reg1, (arith==OP_Add ? "+" : "-"), regVal,
((op==OP_Ge) ? ">=" : (op==OP_Le) ? "<=" : (op==OP_Gt) ? ">" : "<"), reg2
));
- /* Register reg1 currently contains csr1.peerVal (the peer-value from csr1).
- ** This block adds (or subtracts for DESC) the numeric value in regVal
- ** from it. Or, if reg1 is not numeric (it is a NULL, a text value or a blob),
- ** then leave reg1 as it is. In pseudo-code, this is implemented as:
- **
- ** if( reg1>='' ) goto addrGe;
- ** reg1 = reg1 +/- regVal
- ** addrGe:
- **
- ** Since all strings and blobs are greater-than-or-equal-to an empty string,
- ** the add/subtract is skipped for these, as required. If reg1 is a NULL,
- ** then the arithmetic is performed, but since adding or subtracting from
- ** NULL is always NULL anyway, this case is handled as required too. */
- sqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC);
- addrGe = sqlite3VdbeAddOp3(v, OP_Ge, regString, 0, reg1);
- VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, arith, regVal, reg1, reg1);
- sqlite3VdbeJumpHere(v, addrGe);
-
/* If the BIGNULL flag is set for the ORDER BY, then it is required to
** consider NULL values to be larger than all other values, instead of
** the usual smaller. The VDBE opcodes OP_Ge and so on do not handle this
@@ -153302,21 +157056,46 @@ static void windowCodeRangeTest(
break;
default: assert( op==OP_Lt ); /* no-op */ break;
}
- sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+3);
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, addrDone);
/* This block runs if reg1 is not NULL, but reg2 is. */
sqlite3VdbeJumpHere(v, addr);
sqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl); VdbeCoverage(v);
if( op==OP_Gt || op==OP_Ge ){
- sqlite3VdbeChangeP2(v, -1, sqlite3VdbeCurrentAddr(v)+1);
+ sqlite3VdbeChangeP2(v, -1, addrDone);
}
}
+ /* Register reg1 currently contains csr1.peerVal (the peer-value from csr1).
+ ** This block adds (or subtracts for DESC) the numeric value in regVal
+ ** from it. Or, if reg1 is not numeric (it is a NULL, a text value or a blob),
+ ** then leave reg1 as it is. In pseudo-code, this is implemented as:
+ **
+ ** if( reg1>='' ) goto addrGe;
+ ** reg1 = reg1 +/- regVal
+ ** addrGe:
+ **
+ ** Since all strings and blobs are greater-than-or-equal-to an empty string,
+ ** the add/subtract is skipped for these, as required. If reg1 is a NULL,
+ ** then the arithmetic is performed, but since adding or subtracting from
+ ** NULL is always NULL anyway, this case is handled as required too. */
+ sqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC);
+ addrGe = sqlite3VdbeAddOp3(v, OP_Ge, regString, 0, reg1);
+ VdbeCoverage(v);
+ if( (op==OP_Ge && arith==OP_Add) || (op==OP_Le && arith==OP_Subtract) ){
+ sqlite3VdbeAddOp3(v, op, reg2, lbl, reg1); VdbeCoverage(v);
+ }
+ sqlite3VdbeAddOp3(v, arith, regVal, reg1, reg1);
+ sqlite3VdbeJumpHere(v, addrGe);
+
/* Compare registers reg2 and reg1, taking the jump if required. Note that
** control skips over this test if the BIGNULL flag is set and either
** reg1 or reg2 contain a NULL value. */
sqlite3VdbeAddOp3(v, op, reg2, lbl, reg1); VdbeCoverage(v);
+ pColl = sqlite3ExprNNCollSeq(pParse, pOrderBy->a[0].pExpr);
+ sqlite3VdbeAppendP4(v, (void*)pColl, P4_COLLSEQ);
sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
+ sqlite3VdbeResolveLabel(v, addrDone);
assert( op==OP_Ge || op==OP_Gt || op==OP_Lt || op==OP_Le );
testcase(op==OP_Ge); VdbeCoverageIf(v, op==OP_Ge);
@@ -153392,16 +157171,24 @@ static int windowCodeOp(
/* If this is a (RANGE BETWEEN a FOLLOWING AND b FOLLOWING) or
** (RANGE BETWEEN b PRECEDING AND a PRECEDING) frame, ensure the
** start cursor does not advance past the end cursor within the
- ** temporary table. It otherwise might, if (a>b). */
+ ** temporary table. It otherwise might, if (a>b). Also ensure that,
+ ** if the input cursor is still finding new rows, that the end
+ ** cursor does not go past it to EOF. */
if( pMWin->eStart==pMWin->eEnd && regCountdown
- && pMWin->eFrmType==TK_RANGE && op==WINDOW_AGGINVERSE
+ && pMWin->eFrmType==TK_RANGE
){
int regRowid1 = sqlite3GetTempReg(pParse);
int regRowid2 = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1);
- sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2);
- sqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1);
- VdbeCoverage(v);
+ if( op==WINDOW_AGGINVERSE ){
+ sqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1);
+ sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2);
+ sqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1);
+ VdbeCoverage(v);
+ }else if( p->regRowid ){
+ sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid1);
+ sqlite3VdbeAddOp3(v, OP_Ge, p->regRowid, lblDone, regRowid1);
+ VdbeCoverageNeverNull(v);
+ }
sqlite3ReleaseTempReg(pParse, regRowid1);
sqlite3ReleaseTempReg(pParse, regRowid2);
assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING );
@@ -153898,7 +157685,6 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep(
int addrEmpty; /* Address of OP_Rewind in flush: */
int regNew; /* Array of registers holding new input row */
int regRecord; /* regNew array in record form */
- int regRowid; /* Rowid for regRecord in eph table */
int regNewPeer = 0; /* Peer values for new row (part of regNew) */
int regPeer = 0; /* Peer values for current row */
int regFlushPart = 0; /* Register for "Gosub flush_partition" */
@@ -153970,7 +157756,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep(
regNew = pParse->nMem+1;
pParse->nMem += nInput;
regRecord = ++pParse->nMem;
- regRowid = ++pParse->nMem;
+ s.regRowid = ++pParse->nMem;
/* If the window frame contains an "<expr> PRECEDING" or "<expr> FOLLOWING"
** clause, allocate registers to store the results of evaluating each
@@ -154026,9 +157812,9 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep(
}
/* Insert the new row into the ephemeral table */
- sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, regRowid);
- sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, regRowid);
- addrNe = sqlite3VdbeAddOp3(v, OP_Ne, pMWin->regOne, 0, regRowid);
+ sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, s.regRowid);
+ sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, s.regRowid);
+ addrNe = sqlite3VdbeAddOp3(v, OP_Ne, pMWin->regOne, 0, s.regRowid);
VdbeCoverageNeverNull(v);
/* This block is run for the first row of each partition */
@@ -154146,6 +157932,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep(
sqlite3VdbeJumpHere(v, addrGosubFlush);
}
+ s.regRowid = 0;
addrEmpty = sqlite3VdbeAddOp1(v, OP_Rewind, csrWrite);
VdbeCoverage(v);
if( pMWin->eEnd==TK_PRECEDING ){
@@ -154208,8 +157995,10 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep(
/************** End of window.c **********************************************/
/************** Begin file parse.c *******************************************/
+/* This file is automatically generated by Lemon from input grammar
+** source file "parse.y". */
/*
-** 2000-05-29
+** 2001-09-15
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
@@ -154219,22 +158008,15 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep(
** May you share freely, never taking more than you give.
**
*************************************************************************
-** Driver template for the LEMON parser generator.
+** This file contains SQLite's SQL parser.
**
-** The "lemon" program processes an LALR(1) input grammar file, then uses
-** this template to construct a parser. The "lemon" program inserts text
-** at each "%%" line. Also, any "P-a-r-s-e" identifer prefix (without the
-** interstitial "-" characters) contained in this template is changed into
-** the value of the %name directive from the grammar. Otherwise, the content
-** of this template is copied straight through into the generate parser
-** source file.
-**
-** The following is the concatenation of all %include directives from the
-** input grammar file:
+** The canonical source code to this file ("parse.y") is a Lemon grammar
+** file that specifies the input grammar and actions to take while parsing.
+** That input file is processed by Lemon to generate a C-language
+** implementation of a parser for the given grammer. You might be reading
+** this comment as part of the translated C-code. Edits should be made
+** to the original parse.y sources.
*/
-/* #include <stdio.h> */
-/* #include <assert.h> */
-/************ Begin %include sections from the grammar ************************/
/* #include "sqliteInt.h" */
@@ -154327,11 +158109,21 @@ static void updateDeleteLimitError(
static void parserDoubleLinkSelect(Parse *pParse, Select *p){
assert( p!=0 );
if( p->pPrior ){
- Select *pNext = 0, *pLoop;
- int mxSelect, cnt = 0;
- for(pLoop=p; pLoop; pNext=pLoop, pLoop=pLoop->pPrior, cnt++){
+ Select *pNext = 0, *pLoop = p;
+ int mxSelect, cnt = 1;
+ while(1){
pLoop->pNext = pNext;
pLoop->selFlags |= SF_Compound;
+ pNext = pLoop;
+ pLoop = pLoop->pPrior;
+ if( pLoop==0 ) break;
+ cnt++;
+ if( pLoop->pOrderBy || pLoop->pLimit ){
+ sqlite3ErrorMsg(pParse,"%s clause should come after %s not before",
+ pLoop->pOrderBy!=0 ? "ORDER BY" : "LIMIT",
+ sqlite3SelectOpName(pNext->op));
+ break;
+ }
}
if( (p->selFlags & SF_MultiValue)==0 &&
(mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0 &&
@@ -154342,6 +158134,19 @@ static void updateDeleteLimitError(
}
}
+ /* Attach a With object describing the WITH clause to a Select
+ ** object describing the query for which the WITH clause is a prefix.
+ */
+ static Select *attachWithToSelect(Parse *pParse, Select *pSelect, With *pWith){
+ if( pSelect ){
+ pSelect->pWith = pWith;
+ parserDoubleLinkSelect(pParse, pSelect);
+ }else{
+ sqlite3WithDelete(pParse->db, pWith);
+ }
+ return pSelect;
+ }
+
/* Construct a new Expr object from a single identifier. Use the
** new Expr to populate pOut. Set the span of pOut to be the identifier
@@ -154418,11 +158223,194 @@ static void updateDeleteLimitError(
# error too many tokens in the grammar
#endif
/**************** End of %include directives **********************************/
-/* These constants specify the various numeric values for terminal symbols
-** in a format understandable to "makeheaders". This section is blank unless
-** "lemon" is run with the "-m" command-line option.
-***************** Begin makeheaders token definitions *************************/
-/**************** End makeheaders token definitions ***************************/
+/* These constants specify the various numeric values for terminal symbols.
+***************** Begin token definitions *************************************/
+#ifndef TK_SEMI
+#define TK_SEMI 1
+#define TK_EXPLAIN 2
+#define TK_QUERY 3
+#define TK_PLAN 4
+#define TK_BEGIN 5
+#define TK_TRANSACTION 6
+#define TK_DEFERRED 7
+#define TK_IMMEDIATE 8
+#define TK_EXCLUSIVE 9
+#define TK_COMMIT 10
+#define TK_END 11
+#define TK_ROLLBACK 12
+#define TK_SAVEPOINT 13
+#define TK_RELEASE 14
+#define TK_TO 15
+#define TK_TABLE 16
+#define TK_CREATE 17
+#define TK_IF 18
+#define TK_NOT 19
+#define TK_EXISTS 20
+#define TK_TEMP 21
+#define TK_LP 22
+#define TK_RP 23
+#define TK_AS 24
+#define TK_WITHOUT 25
+#define TK_COMMA 26
+#define TK_ABORT 27
+#define TK_ACTION 28
+#define TK_AFTER 29
+#define TK_ANALYZE 30
+#define TK_ASC 31
+#define TK_ATTACH 32
+#define TK_BEFORE 33
+#define TK_BY 34
+#define TK_CASCADE 35
+#define TK_CAST 36
+#define TK_CONFLICT 37
+#define TK_DATABASE 38
+#define TK_DESC 39
+#define TK_DETACH 40
+#define TK_EACH 41
+#define TK_FAIL 42
+#define TK_OR 43
+#define TK_AND 44
+#define TK_IS 45
+#define TK_MATCH 46
+#define TK_LIKE_KW 47
+#define TK_BETWEEN 48
+#define TK_IN 49
+#define TK_ISNULL 50
+#define TK_NOTNULL 51
+#define TK_NE 52
+#define TK_EQ 53
+#define TK_GT 54
+#define TK_LE 55
+#define TK_LT 56
+#define TK_GE 57
+#define TK_ESCAPE 58
+#define TK_ID 59
+#define TK_COLUMNKW 60
+#define TK_DO 61
+#define TK_FOR 62
+#define TK_IGNORE 63
+#define TK_INITIALLY 64
+#define TK_INSTEAD 65
+#define TK_NO 66
+#define TK_KEY 67
+#define TK_OF 68
+#define TK_OFFSET 69
+#define TK_PRAGMA 70
+#define TK_RAISE 71
+#define TK_RECURSIVE 72
+#define TK_REPLACE 73
+#define TK_RESTRICT 74
+#define TK_ROW 75
+#define TK_ROWS 76
+#define TK_TRIGGER 77
+#define TK_VACUUM 78
+#define TK_VIEW 79
+#define TK_VIRTUAL 80
+#define TK_WITH 81
+#define TK_NULLS 82
+#define TK_FIRST 83
+#define TK_LAST 84
+#define TK_CURRENT 85
+#define TK_FOLLOWING 86
+#define TK_PARTITION 87
+#define TK_PRECEDING 88
+#define TK_RANGE 89
+#define TK_UNBOUNDED 90
+#define TK_EXCLUDE 91
+#define TK_GROUPS 92
+#define TK_OTHERS 93
+#define TK_TIES 94
+#define TK_GENERATED 95
+#define TK_ALWAYS 96
+#define TK_MATERIALIZED 97
+#define TK_REINDEX 98
+#define TK_RENAME 99
+#define TK_CTIME_KW 100
+#define TK_ANY 101
+#define TK_BITAND 102
+#define TK_BITOR 103
+#define TK_LSHIFT 104
+#define TK_RSHIFT 105
+#define TK_PLUS 106
+#define TK_MINUS 107
+#define TK_STAR 108
+#define TK_SLASH 109
+#define TK_REM 110
+#define TK_CONCAT 111
+#define TK_COLLATE 112
+#define TK_BITNOT 113
+#define TK_ON 114
+#define TK_INDEXED 115
+#define TK_STRING 116
+#define TK_JOIN_KW 117
+#define TK_CONSTRAINT 118
+#define TK_DEFAULT 119
+#define TK_NULL 120
+#define TK_PRIMARY 121
+#define TK_UNIQUE 122
+#define TK_CHECK 123
+#define TK_REFERENCES 124
+#define TK_AUTOINCR 125
+#define TK_INSERT 126
+#define TK_DELETE 127
+#define TK_UPDATE 128
+#define TK_SET 129
+#define TK_DEFERRABLE 130
+#define TK_FOREIGN 131
+#define TK_DROP 132
+#define TK_UNION 133
+#define TK_ALL 134
+#define TK_EXCEPT 135
+#define TK_INTERSECT 136
+#define TK_SELECT 137
+#define TK_VALUES 138
+#define TK_DISTINCT 139
+#define TK_DOT 140
+#define TK_FROM 141
+#define TK_JOIN 142
+#define TK_USING 143
+#define TK_ORDER 144
+#define TK_GROUP 145
+#define TK_HAVING 146
+#define TK_LIMIT 147
+#define TK_WHERE 148
+#define TK_RETURNING 149
+#define TK_INTO 150
+#define TK_NOTHING 151
+#define TK_FLOAT 152
+#define TK_BLOB 153
+#define TK_INTEGER 154
+#define TK_VARIABLE 155
+#define TK_CASE 156
+#define TK_WHEN 157
+#define TK_THEN 158
+#define TK_ELSE 159
+#define TK_INDEX 160
+#define TK_ALTER 161
+#define TK_ADD 162
+#define TK_WINDOW 163
+#define TK_OVER 164
+#define TK_FILTER 165
+#define TK_COLUMN 166
+#define TK_AGG_FUNCTION 167
+#define TK_AGG_COLUMN 168
+#define TK_TRUEFALSE 169
+#define TK_ISNOT 170
+#define TK_FUNCTION 171
+#define TK_UMINUS 172
+#define TK_UPLUS 173
+#define TK_TRUTH 174
+#define TK_REGISTER 175
+#define TK_VECTOR 176
+#define TK_SELECT_COLUMN 177
+#define TK_IF_NULL_ROW 178
+#define TK_ASTERISK 179
+#define TK_SPAN 180
+#define TK_ERROR 181
+#define TK_SPACE 182
+#define TK_ILLEGAL 183
+#endif
+/**************** End token definitions ***************************************/
/* The next sections is a series of control #defines.
** various aspects of the generated parser.
@@ -154480,28 +158468,29 @@ static void updateDeleteLimitError(
#endif
/************* Begin control #defines *****************************************/
#define YYCODETYPE unsigned short int
-#define YYNOCODE 310
+#define YYNOCODE 317
#define YYACTIONTYPE unsigned short int
-#define YYWILDCARD 100
+#define YYWILDCARD 101
#define sqlite3ParserTOKENTYPE Token
typedef union {
int yyinit;
sqlite3ParserTOKENTYPE yy0;
- SrcList* yy47;
- u8 yy58;
- struct FrameBound yy77;
- With* yy131;
- int yy192;
- Expr* yy202;
- struct {int value; int mask;} yy207;
- struct TrigEvent yy230;
- ExprList* yy242;
- Window* yy303;
- Upsert* yy318;
- const char* yy436;
- TriggerStep* yy447;
- Select* yy539;
- IdList* yy600;
+ Window* yy49;
+ ExprList* yy70;
+ Select* yy81;
+ With* yy103;
+ struct FrameBound yy117;
+ struct {int value; int mask;} yy139;
+ SrcList* yy153;
+ TriggerStep* yy157;
+ Upsert* yy190;
+ struct TrigEvent yy262;
+ Cte* yy329;
+ int yy376;
+ Expr* yy404;
+ IdList* yy436;
+ const char* yy504;
+ u8 yy552;
} YYMINORTYPE;
#ifndef YYSTACKDEPTH
#define YYSTACKDEPTH 100
@@ -154517,18 +158506,18 @@ typedef union {
#define sqlite3ParserCTX_FETCH Parse *pParse=yypParser->pParse;
#define sqlite3ParserCTX_STORE yypParser->pParse=pParse;
#define YYFALLBACK 1
-#define YYNSTATE 553
-#define YYNRULE 385
-#define YYNRULE_WITH_ACTION 325
-#define YYNTOKEN 181
-#define YY_MAX_SHIFT 552
-#define YY_MIN_SHIFTREDUCE 803
-#define YY_MAX_SHIFTREDUCE 1187
-#define YY_ERROR_ACTION 1188
-#define YY_ACCEPT_ACTION 1189
-#define YY_NO_ACTION 1190
-#define YY_MIN_REDUCE 1191
-#define YY_MAX_REDUCE 1575
+#define YYNSTATE 570
+#define YYNRULE 398
+#define YYNRULE_WITH_ACTION 337
+#define YYNTOKEN 184
+#define YY_MAX_SHIFT 569
+#define YY_MIN_SHIFTREDUCE 825
+#define YY_MAX_SHIFTREDUCE 1222
+#define YY_ERROR_ACTION 1223
+#define YY_ACCEPT_ACTION 1224
+#define YY_NO_ACTION 1225
+#define YY_MIN_REDUCE 1226
+#define YY_MAX_REDUCE 1623
/************* End control #defines *******************************************/
#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])))
@@ -154595,586 +158584,601 @@ typedef union {
** yy_default[] Default action for each state.
**
*********** Begin parsing tables **********************************************/
-#define YY_ACTTAB_COUNT (1962)
+#define YY_ACTTAB_COUNT (2023)
static const YYACTIONTYPE yy_action[] = {
- /* 0 */ 546, 1222, 546, 451, 1260, 546, 1239, 546, 114, 111,
- /* 10 */ 211, 546, 1537, 546, 1260, 523, 114, 111, 211, 392,
- /* 20 */ 1232, 344, 42, 42, 42, 42, 1225, 42, 42, 71,
- /* 30 */ 71, 937, 1224, 71, 71, 71, 71, 1462, 1493, 938,
- /* 40 */ 820, 453, 6, 121, 122, 112, 1165, 1165, 1006, 1009,
- /* 50 */ 999, 999, 119, 119, 120, 120, 120, 120, 1543, 392,
- /* 60 */ 1358, 1517, 552, 2, 1193, 194, 528, 436, 143, 291,
- /* 70 */ 528, 136, 528, 371, 261, 504, 272, 385, 1273, 527,
- /* 80 */ 503, 493, 164, 121, 122, 112, 1165, 1165, 1006, 1009,
- /* 90 */ 999, 999, 119, 119, 120, 120, 120, 120, 1358, 442,
- /* 100 */ 1514, 118, 118, 118, 118, 117, 117, 116, 116, 116,
- /* 110 */ 115, 424, 266, 266, 266, 266, 1498, 358, 1500, 435,
- /* 120 */ 357, 1498, 517, 524, 1485, 543, 1114, 543, 1114, 392,
- /* 130 */ 405, 241, 208, 114, 111, 211, 98, 290, 537, 221,
- /* 140 */ 1029, 118, 118, 118, 118, 117, 117, 116, 116, 116,
- /* 150 */ 115, 424, 1142, 121, 122, 112, 1165, 1165, 1006, 1009,
- /* 160 */ 999, 999, 119, 119, 120, 120, 120, 120, 406, 428,
- /* 170 */ 117, 117, 116, 116, 116, 115, 424, 1418, 468, 123,
- /* 180 */ 118, 118, 118, 118, 117, 117, 116, 116, 116, 115,
- /* 190 */ 424, 116, 116, 116, 115, 424, 540, 540, 540, 392,
- /* 200 */ 505, 120, 120, 120, 120, 113, 1051, 1142, 1143, 1144,
- /* 210 */ 1051, 118, 118, 118, 118, 117, 117, 116, 116, 116,
- /* 220 */ 115, 424, 1461, 121, 122, 112, 1165, 1165, 1006, 1009,
- /* 230 */ 999, 999, 119, 119, 120, 120, 120, 120, 392, 444,
- /* 240 */ 316, 83, 463, 81, 359, 382, 1142, 80, 118, 118,
- /* 250 */ 118, 118, 117, 117, 116, 116, 116, 115, 424, 179,
- /* 260 */ 434, 424, 121, 122, 112, 1165, 1165, 1006, 1009, 999,
- /* 270 */ 999, 119, 119, 120, 120, 120, 120, 434, 433, 266,
- /* 280 */ 266, 118, 118, 118, 118, 117, 117, 116, 116, 116,
- /* 290 */ 115, 424, 543, 1109, 903, 506, 1142, 114, 111, 211,
- /* 300 */ 1431, 1142, 1143, 1144, 206, 491, 1109, 392, 449, 1109,
- /* 310 */ 545, 330, 120, 120, 120, 120, 298, 1431, 1433, 17,
- /* 320 */ 118, 118, 118, 118, 117, 117, 116, 116, 116, 115,
- /* 330 */ 424, 121, 122, 112, 1165, 1165, 1006, 1009, 999, 999,
- /* 340 */ 119, 119, 120, 120, 120, 120, 392, 1358, 434, 1142,
- /* 350 */ 482, 1142, 1143, 1144, 996, 996, 1007, 1010, 445, 118,
- /* 360 */ 118, 118, 118, 117, 117, 116, 116, 116, 115, 424,
- /* 370 */ 121, 122, 112, 1165, 1165, 1006, 1009, 999, 999, 119,
- /* 380 */ 119, 120, 120, 120, 120, 1054, 1054, 465, 1431, 118,
- /* 390 */ 118, 118, 118, 117, 117, 116, 116, 116, 115, 424,
- /* 400 */ 1142, 451, 546, 1426, 1142, 1143, 1144, 233, 966, 1142,
- /* 410 */ 481, 478, 477, 171, 360, 392, 164, 407, 414, 842,
- /* 420 */ 476, 164, 185, 334, 71, 71, 1243, 1000, 118, 118,
- /* 430 */ 118, 118, 117, 117, 116, 116, 116, 115, 424, 121,
- /* 440 */ 122, 112, 1165, 1165, 1006, 1009, 999, 999, 119, 119,
- /* 450 */ 120, 120, 120, 120, 392, 1142, 1143, 1144, 835, 12,
- /* 460 */ 314, 509, 163, 356, 1142, 1143, 1144, 114, 111, 211,
- /* 470 */ 508, 290, 537, 546, 276, 180, 290, 537, 121, 122,
- /* 480 */ 112, 1165, 1165, 1006, 1009, 999, 999, 119, 119, 120,
- /* 490 */ 120, 120, 120, 345, 484, 71, 71, 118, 118, 118,
- /* 500 */ 118, 117, 117, 116, 116, 116, 115, 424, 1142, 209,
- /* 510 */ 411, 523, 1142, 1109, 1571, 378, 252, 269, 342, 487,
- /* 520 */ 337, 486, 238, 392, 513, 364, 1109, 1127, 333, 1109,
- /* 530 */ 191, 409, 286, 32, 457, 443, 118, 118, 118, 118,
- /* 540 */ 117, 117, 116, 116, 116, 115, 424, 121, 122, 112,
- /* 550 */ 1165, 1165, 1006, 1009, 999, 999, 119, 119, 120, 120,
- /* 560 */ 120, 120, 392, 1142, 1143, 1144, 987, 1142, 1143, 1144,
- /* 570 */ 1142, 233, 492, 1492, 481, 478, 477, 6, 163, 546,
- /* 580 */ 512, 546, 115, 424, 476, 5, 121, 122, 112, 1165,
- /* 590 */ 1165, 1006, 1009, 999, 999, 119, 119, 120, 120, 120,
- /* 600 */ 120, 13, 13, 13, 13, 118, 118, 118, 118, 117,
- /* 610 */ 117, 116, 116, 116, 115, 424, 403, 502, 408, 546,
- /* 620 */ 1486, 544, 1142, 892, 892, 1142, 1143, 1144, 1473, 1142,
- /* 630 */ 275, 392, 808, 809, 810, 971, 422, 422, 422, 16,
- /* 640 */ 16, 55, 55, 1242, 118, 118, 118, 118, 117, 117,
- /* 650 */ 116, 116, 116, 115, 424, 121, 122, 112, 1165, 1165,
- /* 660 */ 1006, 1009, 999, 999, 119, 119, 120, 120, 120, 120,
- /* 670 */ 392, 1189, 1, 1, 552, 2, 1193, 1142, 1143, 1144,
- /* 680 */ 194, 291, 898, 136, 1142, 1143, 1144, 897, 521, 1492,
- /* 690 */ 1273, 3, 380, 6, 121, 122, 112, 1165, 1165, 1006,
- /* 700 */ 1009, 999, 999, 119, 119, 120, 120, 120, 120, 858,
- /* 710 */ 546, 924, 546, 118, 118, 118, 118, 117, 117, 116,
- /* 720 */ 116, 116, 115, 424, 266, 266, 1092, 1569, 1142, 551,
- /* 730 */ 1569, 1193, 13, 13, 13, 13, 291, 543, 136, 392,
- /* 740 */ 485, 421, 420, 966, 344, 1273, 468, 410, 859, 279,
- /* 750 */ 140, 221, 118, 118, 118, 118, 117, 117, 116, 116,
- /* 760 */ 116, 115, 424, 121, 122, 112, 1165, 1165, 1006, 1009,
- /* 770 */ 999, 999, 119, 119, 120, 120, 120, 120, 546, 266,
- /* 780 */ 266, 428, 392, 1142, 1143, 1144, 1172, 830, 1172, 468,
- /* 790 */ 431, 145, 543, 1146, 401, 314, 439, 302, 838, 1490,
- /* 800 */ 71, 71, 412, 6, 1090, 473, 221, 100, 112, 1165,
- /* 810 */ 1165, 1006, 1009, 999, 999, 119, 119, 120, 120, 120,
- /* 820 */ 120, 118, 118, 118, 118, 117, 117, 116, 116, 116,
- /* 830 */ 115, 424, 237, 1425, 546, 451, 428, 287, 986, 546,
- /* 840 */ 236, 235, 234, 830, 97, 529, 429, 1265, 1265, 1146,
- /* 850 */ 494, 307, 430, 838, 977, 546, 71, 71, 976, 1241,
- /* 860 */ 546, 51, 51, 300, 118, 118, 118, 118, 117, 117,
- /* 870 */ 116, 116, 116, 115, 424, 194, 103, 70, 70, 266,
- /* 880 */ 266, 546, 71, 71, 266, 266, 30, 391, 344, 976,
- /* 890 */ 976, 978, 543, 528, 1109, 328, 392, 543, 495, 397,
- /* 900 */ 1470, 195, 530, 13, 13, 1358, 240, 1109, 277, 280,
- /* 910 */ 1109, 280, 304, 457, 306, 333, 392, 31, 188, 419,
- /* 920 */ 121, 122, 112, 1165, 1165, 1006, 1009, 999, 999, 119,
- /* 930 */ 119, 120, 120, 120, 120, 142, 392, 365, 457, 986,
- /* 940 */ 121, 122, 112, 1165, 1165, 1006, 1009, 999, 999, 119,
- /* 950 */ 119, 120, 120, 120, 120, 977, 323, 1142, 326, 976,
- /* 960 */ 121, 110, 112, 1165, 1165, 1006, 1009, 999, 999, 119,
- /* 970 */ 119, 120, 120, 120, 120, 464, 377, 1185, 118, 118,
- /* 980 */ 118, 118, 117, 117, 116, 116, 116, 115, 424, 1142,
- /* 990 */ 976, 976, 978, 305, 9, 366, 244, 362, 118, 118,
- /* 1000 */ 118, 118, 117, 117, 116, 116, 116, 115, 424, 313,
- /* 1010 */ 546, 344, 1142, 1143, 1144, 299, 290, 537, 118, 118,
- /* 1020 */ 118, 118, 117, 117, 116, 116, 116, 115, 424, 1263,
- /* 1030 */ 1263, 1163, 13, 13, 278, 421, 420, 468, 392, 923,
- /* 1040 */ 260, 260, 289, 1169, 1142, 1143, 1144, 189, 1171, 266,
- /* 1050 */ 266, 468, 390, 543, 1186, 546, 1170, 263, 144, 489,
- /* 1060 */ 922, 546, 543, 122, 112, 1165, 1165, 1006, 1009, 999,
- /* 1070 */ 999, 119, 119, 120, 120, 120, 120, 71, 71, 1142,
- /* 1080 */ 1172, 1272, 1172, 13, 13, 898, 1070, 1163, 546, 468,
- /* 1090 */ 897, 107, 538, 1491, 4, 1268, 1109, 6, 525, 1049,
- /* 1100 */ 12, 1071, 1092, 1570, 312, 455, 1570, 520, 541, 1109,
- /* 1110 */ 56, 56, 1109, 1489, 423, 1358, 1072, 6, 345, 285,
- /* 1120 */ 118, 118, 118, 118, 117, 117, 116, 116, 116, 115,
- /* 1130 */ 424, 425, 1271, 321, 1142, 1143, 1144, 878, 266, 266,
- /* 1140 */ 1277, 107, 538, 535, 4, 1488, 293, 879, 1211, 6,
- /* 1150 */ 210, 543, 543, 164, 294, 496, 416, 204, 541, 267,
- /* 1160 */ 267, 1214, 398, 511, 499, 204, 266, 266, 396, 531,
- /* 1170 */ 8, 986, 543, 519, 546, 922, 458, 105, 105, 543,
- /* 1180 */ 1090, 425, 266, 266, 106, 417, 425, 548, 547, 266,
- /* 1190 */ 266, 976, 518, 535, 1373, 543, 15, 15, 266, 266,
- /* 1200 */ 456, 1120, 543, 266, 266, 1070, 1372, 515, 290, 537,
- /* 1210 */ 546, 543, 514, 97, 444, 316, 543, 546, 922, 125,
- /* 1220 */ 1071, 986, 976, 976, 978, 979, 27, 105, 105, 401,
- /* 1230 */ 343, 1511, 44, 44, 106, 1072, 425, 548, 547, 57,
- /* 1240 */ 57, 976, 343, 1511, 107, 538, 546, 4, 462, 401,
- /* 1250 */ 214, 1120, 459, 297, 377, 1091, 534, 1309, 546, 539,
- /* 1260 */ 398, 541, 290, 537, 104, 244, 102, 526, 58, 58,
- /* 1270 */ 546, 199, 976, 976, 978, 979, 27, 1516, 1131, 427,
- /* 1280 */ 59, 59, 270, 237, 425, 138, 95, 375, 375, 374,
- /* 1290 */ 255, 372, 60, 60, 817, 1180, 535, 546, 273, 546,
- /* 1300 */ 1163, 1308, 389, 388, 546, 438, 546, 215, 210, 296,
- /* 1310 */ 515, 849, 546, 265, 208, 516, 1476, 295, 274, 61,
- /* 1320 */ 61, 62, 62, 308, 986, 109, 45, 45, 46, 46,
- /* 1330 */ 105, 105, 1186, 922, 47, 47, 341, 106, 546, 425,
- /* 1340 */ 548, 547, 1542, 546, 976, 867, 340, 217, 546, 937,
- /* 1350 */ 397, 107, 538, 218, 4, 156, 1163, 938, 158, 546,
- /* 1360 */ 49, 49, 1162, 546, 268, 50, 50, 546, 541, 1450,
- /* 1370 */ 63, 63, 546, 1449, 216, 976, 976, 978, 979, 27,
- /* 1380 */ 446, 64, 64, 546, 460, 65, 65, 546, 318, 14,
- /* 1390 */ 14, 425, 1305, 546, 66, 66, 1087, 546, 141, 379,
- /* 1400 */ 38, 546, 963, 535, 322, 127, 127, 546, 393, 67,
- /* 1410 */ 67, 546, 325, 290, 537, 52, 52, 515, 546, 68,
- /* 1420 */ 68, 845, 514, 69, 69, 399, 165, 857, 856, 53,
- /* 1430 */ 53, 986, 311, 151, 151, 97, 432, 105, 105, 327,
- /* 1440 */ 152, 152, 526, 1048, 106, 1048, 425, 548, 547, 1131,
- /* 1450 */ 427, 976, 1032, 270, 968, 239, 329, 243, 375, 375,
- /* 1460 */ 374, 255, 372, 940, 941, 817, 1296, 546, 220, 546,
- /* 1470 */ 107, 538, 546, 4, 546, 1256, 199, 845, 215, 1036,
- /* 1480 */ 296, 1530, 976, 976, 978, 979, 27, 541, 295, 76,
- /* 1490 */ 76, 54, 54, 980, 72, 72, 128, 128, 864, 865,
- /* 1500 */ 107, 538, 546, 4, 1047, 546, 1047, 533, 469, 546,
- /* 1510 */ 425, 546, 450, 1240, 546, 243, 546, 541, 217, 546,
- /* 1520 */ 452, 197, 535, 243, 73, 73, 156, 129, 129, 158,
- /* 1530 */ 336, 130, 130, 126, 126, 1036, 150, 150, 149, 149,
- /* 1540 */ 425, 134, 134, 317, 474, 216, 97, 239, 331, 980,
- /* 1550 */ 986, 97, 535, 346, 347, 546, 105, 105, 902, 931,
- /* 1560 */ 546, 895, 243, 106, 109, 425, 548, 547, 546, 1505,
- /* 1570 */ 976, 828, 99, 538, 139, 4, 546, 133, 133, 393,
- /* 1580 */ 986, 1317, 131, 131, 290, 537, 105, 105, 1357, 541,
- /* 1590 */ 132, 132, 1292, 106, 1303, 425, 548, 547, 75, 75,
- /* 1600 */ 976, 976, 976, 978, 979, 27, 546, 432, 896, 1289,
- /* 1610 */ 532, 109, 425, 1363, 546, 1221, 1213, 1202, 258, 546,
- /* 1620 */ 349, 546, 1201, 11, 535, 1203, 1524, 351, 77, 77,
- /* 1630 */ 376, 976, 976, 978, 979, 27, 74, 74, 353, 213,
- /* 1640 */ 301, 43, 43, 48, 48, 437, 310, 201, 303, 1350,
- /* 1650 */ 315, 355, 986, 454, 479, 1239, 339, 192, 105, 105,
- /* 1660 */ 1422, 1421, 193, 536, 205, 106, 1527, 425, 548, 547,
- /* 1670 */ 1180, 167, 976, 270, 247, 1469, 1467, 1177, 375, 375,
- /* 1680 */ 374, 255, 372, 200, 369, 817, 400, 83, 79, 82,
- /* 1690 */ 1427, 448, 177, 95, 1342, 161, 169, 1339, 215, 440,
- /* 1700 */ 296, 172, 173, 976, 976, 978, 979, 27, 295, 174,
- /* 1710 */ 175, 441, 472, 223, 1347, 383, 35, 381, 36, 461,
- /* 1720 */ 88, 1353, 181, 447, 384, 1416, 227, 467, 259, 229,
- /* 1730 */ 186, 488, 470, 324, 1250, 230, 231, 320, 217, 1204,
- /* 1740 */ 1438, 1259, 386, 1258, 413, 90, 156, 849, 1541, 158,
- /* 1750 */ 206, 415, 1540, 507, 1300, 1257, 94, 348, 1229, 1301,
- /* 1760 */ 387, 1510, 1228, 338, 1227, 216, 350, 1539, 498, 283,
- /* 1770 */ 284, 1249, 501, 1299, 352, 245, 246, 418, 1298, 354,
- /* 1780 */ 1496, 1495, 124, 10, 526, 363, 101, 1324, 253, 96,
- /* 1790 */ 510, 1210, 34, 549, 1137, 254, 256, 257, 166, 393,
- /* 1800 */ 550, 1199, 1282, 361, 290, 537, 1281, 196, 367, 368,
- /* 1810 */ 1194, 153, 1454, 137, 281, 1323, 1455, 804, 154, 426,
- /* 1820 */ 198, 155, 1453, 1452, 292, 212, 202, 432, 1402, 203,
- /* 1830 */ 271, 135, 288, 78, 1046, 1044, 960, 168, 157, 881,
- /* 1840 */ 170, 219, 309, 222, 1060, 176, 964, 159, 402, 84,
- /* 1850 */ 178, 404, 85, 86, 87, 160, 1063, 224, 394, 395,
- /* 1860 */ 225, 1059, 146, 18, 226, 319, 243, 1174, 466, 228,
- /* 1870 */ 1052, 182, 183, 37, 819, 471, 340, 232, 332, 483,
- /* 1880 */ 184, 89, 162, 19, 20, 475, 91, 480, 847, 335,
- /* 1890 */ 147, 860, 282, 92, 490, 93, 1125, 148, 1012, 1095,
- /* 1900 */ 39, 497, 1096, 40, 500, 262, 207, 264, 930, 187,
- /* 1910 */ 925, 109, 1111, 1115, 1113, 7, 1099, 242, 33, 1119,
- /* 1920 */ 21, 522, 22, 23, 24, 1118, 25, 190, 97, 26,
- /* 1930 */ 1027, 1013, 1011, 1015, 1069, 1016, 1068, 249, 248, 28,
- /* 1940 */ 41, 891, 981, 829, 108, 29, 250, 542, 251, 370,
- /* 1950 */ 373, 1133, 1132, 1190, 1190, 1190, 1190, 1190, 1190, 1190,
- /* 1960 */ 1532, 1531,
+ /* 0 */ 563, 1295, 563, 1274, 168, 1257, 115, 112, 218, 373,
+ /* 10 */ 563, 1295, 374, 563, 488, 563, 115, 112, 218, 406,
+ /* 20 */ 1300, 1300, 41, 41, 41, 41, 514, 1504, 520, 1298,
+ /* 30 */ 1298, 959, 41, 41, 1260, 71, 71, 51, 51, 960,
+ /* 40 */ 557, 557, 557, 122, 123, 113, 1200, 1200, 1035, 1038,
+ /* 50 */ 1028, 1028, 120, 120, 121, 121, 121, 121, 414, 406,
+ /* 60 */ 273, 273, 273, 273, 115, 112, 218, 115, 112, 218,
+ /* 70 */ 197, 268, 545, 560, 515, 560, 211, 563, 385, 248,
+ /* 80 */ 215, 521, 399, 122, 123, 113, 1200, 1200, 1035, 1038,
+ /* 90 */ 1028, 1028, 120, 120, 121, 121, 121, 121, 540, 13,
+ /* 100 */ 13, 1259, 119, 119, 119, 119, 118, 118, 117, 117,
+ /* 110 */ 117, 116, 441, 1176, 419, 197, 446, 320, 512, 1539,
+ /* 120 */ 1545, 372, 1547, 6, 371, 1176, 1148, 394, 1148, 406,
+ /* 130 */ 1545, 534, 115, 112, 218, 1415, 99, 30, 121, 121,
+ /* 140 */ 121, 121, 119, 119, 119, 119, 118, 118, 117, 117,
+ /* 150 */ 117, 116, 441, 122, 123, 113, 1200, 1200, 1035, 1038,
+ /* 160 */ 1028, 1028, 120, 120, 121, 121, 121, 121, 31, 1176,
+ /* 170 */ 1177, 1178, 241, 357, 1558, 501, 498, 497, 317, 124,
+ /* 180 */ 319, 1176, 1177, 1178, 1176, 496, 119, 119, 119, 119,
+ /* 190 */ 118, 118, 117, 117, 117, 116, 441, 139, 96, 406,
+ /* 200 */ 121, 121, 121, 121, 114, 117, 117, 117, 116, 441,
+ /* 210 */ 541, 1532, 119, 119, 119, 119, 118, 118, 117, 117,
+ /* 220 */ 117, 116, 441, 122, 123, 113, 1200, 1200, 1035, 1038,
+ /* 230 */ 1028, 1028, 120, 120, 121, 121, 121, 121, 406, 441,
+ /* 240 */ 1176, 1177, 1178, 81, 439, 439, 439, 80, 119, 119,
+ /* 250 */ 119, 119, 118, 118, 117, 117, 117, 116, 441, 488,
+ /* 260 */ 1176, 318, 122, 123, 113, 1200, 1200, 1035, 1038, 1028,
+ /* 270 */ 1028, 120, 120, 121, 121, 121, 121, 493, 1025, 1025,
+ /* 280 */ 1036, 1039, 119, 119, 119, 119, 118, 118, 117, 117,
+ /* 290 */ 117, 116, 441, 1584, 995, 1224, 1, 1, 569, 2,
+ /* 300 */ 1228, 1267, 137, 1503, 245, 305, 473, 140, 406, 860,
+ /* 310 */ 561, 1176, 914, 914, 1308, 359, 1176, 1177, 1178, 462,
+ /* 320 */ 330, 119, 119, 119, 119, 118, 118, 117, 117, 117,
+ /* 330 */ 116, 441, 122, 123, 113, 1200, 1200, 1035, 1038, 1028,
+ /* 340 */ 1028, 120, 120, 121, 121, 121, 121, 328, 273, 273,
+ /* 350 */ 1015, 83, 1029, 425, 1564, 569, 2, 1228, 304, 554,
+ /* 360 */ 925, 560, 305, 944, 140, 860, 1006, 1176, 1177, 1178,
+ /* 370 */ 1005, 1308, 411, 213, 511, 229, 119, 119, 119, 119,
+ /* 380 */ 118, 118, 117, 117, 117, 116, 441, 519, 347, 116,
+ /* 390 */ 441, 119, 119, 119, 119, 118, 118, 117, 117, 117,
+ /* 400 */ 116, 441, 1005, 1005, 1007, 273, 273, 445, 563, 16,
+ /* 410 */ 16, 1590, 563, 1540, 563, 406, 1176, 6, 560, 344,
+ /* 420 */ 182, 118, 118, 117, 117, 117, 116, 441, 416, 142,
+ /* 430 */ 71, 71, 229, 563, 71, 71, 55, 55, 203, 122,
+ /* 440 */ 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120,
+ /* 450 */ 121, 121, 121, 121, 217, 13, 13, 1176, 406, 568,
+ /* 460 */ 1400, 1228, 502, 137, 445, 168, 305, 545, 140, 1180,
+ /* 470 */ 424, 545, 1176, 1177, 1178, 1308, 544, 438, 437, 944,
+ /* 480 */ 513, 452, 122, 123, 113, 1200, 1200, 1035, 1038, 1028,
+ /* 490 */ 1028, 120, 120, 121, 121, 121, 121, 315, 119, 119,
+ /* 500 */ 119, 119, 118, 118, 117, 117, 117, 116, 441, 273,
+ /* 510 */ 273, 1143, 416, 1176, 1177, 1178, 543, 563, 1143, 304,
+ /* 520 */ 554, 1561, 560, 1207, 1143, 1207, 1180, 1143, 406, 530,
+ /* 530 */ 421, 1143, 864, 183, 1143, 143, 229, 562, 32, 71,
+ /* 540 */ 71, 119, 119, 119, 119, 118, 118, 117, 117, 117,
+ /* 550 */ 116, 441, 122, 123, 113, 1200, 1200, 1035, 1038, 1028,
+ /* 560 */ 1028, 120, 120, 121, 121, 121, 121, 406, 445, 241,
+ /* 570 */ 1176, 857, 501, 498, 497, 1176, 526, 189, 245, 538,
+ /* 580 */ 1539, 282, 496, 370, 6, 563, 529, 477, 5, 279,
+ /* 590 */ 1015, 122, 123, 113, 1200, 1200, 1035, 1038, 1028, 1028,
+ /* 600 */ 120, 120, 121, 121, 121, 121, 1006, 13, 13, 1414,
+ /* 610 */ 1005, 119, 119, 119, 119, 118, 118, 117, 117, 117,
+ /* 620 */ 116, 441, 426, 273, 273, 1176, 1176, 1177, 1178, 1619,
+ /* 630 */ 392, 1176, 1177, 1178, 1176, 342, 560, 406, 525, 361,
+ /* 640 */ 430, 1161, 1005, 1005, 1007, 348, 411, 357, 1558, 488,
+ /* 650 */ 119, 119, 119, 119, 118, 118, 117, 117, 117, 116,
+ /* 660 */ 441, 122, 123, 113, 1200, 1200, 1035, 1038, 1028, 1028,
+ /* 670 */ 120, 120, 121, 121, 121, 121, 406, 830, 831, 832,
+ /* 680 */ 1016, 1176, 1177, 1178, 396, 285, 148, 1312, 304, 554,
+ /* 690 */ 1176, 1177, 1178, 1467, 216, 3, 337, 137, 340, 560,
+ /* 700 */ 122, 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, 120,
+ /* 710 */ 120, 121, 121, 121, 121, 563, 504, 946, 273, 273,
+ /* 720 */ 119, 119, 119, 119, 118, 118, 117, 117, 117, 116,
+ /* 730 */ 441, 560, 1176, 427, 563, 451, 98, 13, 13, 259,
+ /* 740 */ 276, 356, 507, 351, 506, 246, 406, 361, 469, 1530,
+ /* 750 */ 1000, 347, 293, 304, 554, 1589, 71, 71, 889, 119,
+ /* 760 */ 119, 119, 119, 118, 118, 117, 117, 117, 116, 441,
+ /* 770 */ 122, 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, 120,
+ /* 780 */ 120, 121, 121, 121, 121, 406, 1143, 1078, 1176, 1177,
+ /* 790 */ 1178, 416, 1080, 300, 150, 995, 1080, 361, 361, 1143,
+ /* 800 */ 361, 378, 1143, 477, 563, 244, 243, 242, 1278, 122,
+ /* 810 */ 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120,
+ /* 820 */ 121, 121, 121, 121, 563, 880, 13, 13, 483, 119,
+ /* 830 */ 119, 119, 119, 118, 118, 117, 117, 117, 116, 441,
+ /* 840 */ 1176, 191, 540, 563, 147, 149, 13, 13, 328, 457,
+ /* 850 */ 316, 1083, 1083, 485, 1537, 406, 505, 1530, 6, 1514,
+ /* 860 */ 284, 192, 1277, 145, 881, 71, 71, 488, 119, 119,
+ /* 870 */ 119, 119, 118, 118, 117, 117, 117, 116, 441, 122,
+ /* 880 */ 123, 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120,
+ /* 890 */ 121, 121, 121, 121, 563, 471, 1176, 1177, 1178, 406,
+ /* 900 */ 852, 327, 301, 462, 330, 1516, 270, 1530, 1530, 944,
+ /* 910 */ 1531, 1307, 313, 9, 842, 251, 71, 71, 477, 428,
+ /* 920 */ 146, 488, 38, 945, 101, 113, 1200, 1200, 1035, 1038,
+ /* 930 */ 1028, 1028, 120, 120, 121, 121, 121, 121, 119, 119,
+ /* 940 */ 119, 119, 118, 118, 117, 117, 117, 116, 441, 563,
+ /* 950 */ 1197, 1099, 563, 436, 563, 1533, 563, 852, 1122, 1617,
+ /* 960 */ 454, 290, 1617, 546, 251, 1303, 1100, 267, 267, 281,
+ /* 970 */ 404, 70, 70, 460, 71, 71, 71, 71, 13, 13,
+ /* 980 */ 560, 1101, 119, 119, 119, 119, 118, 118, 117, 117,
+ /* 990 */ 117, 116, 441, 542, 104, 273, 273, 273, 273, 1197,
+ /* 1000 */ 217, 1468, 900, 471, 450, 563, 1473, 1197, 560, 447,
+ /* 1010 */ 560, 545, 901, 440, 406, 1058, 292, 274, 274, 198,
+ /* 1020 */ 547, 450, 449, 1473, 1475, 944, 455, 56, 56, 410,
+ /* 1030 */ 560, 1122, 1618, 379, 406, 1618, 404, 1120, 122, 123,
+ /* 1040 */ 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120, 121,
+ /* 1050 */ 121, 121, 121, 1460, 406, 12, 1197, 1512, 122, 123,
+ /* 1060 */ 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120, 121,
+ /* 1070 */ 121, 121, 121, 308, 471, 126, 359, 286, 122, 111,
+ /* 1080 */ 113, 1200, 1200, 1035, 1038, 1028, 1028, 120, 120, 121,
+ /* 1090 */ 121, 121, 121, 309, 450, 471, 1473, 119, 119, 119,
+ /* 1100 */ 119, 118, 118, 117, 117, 117, 116, 441, 1176, 563,
+ /* 1110 */ 1120, 482, 563, 312, 433, 479, 197, 119, 119, 119,
+ /* 1120 */ 119, 118, 118, 117, 117, 117, 116, 441, 405, 12,
+ /* 1130 */ 536, 15, 15, 478, 43, 43, 509, 119, 119, 119,
+ /* 1140 */ 119, 118, 118, 117, 117, 117, 116, 441, 289, 535,
+ /* 1150 */ 294, 563, 294, 391, 1220, 438, 437, 406, 1154, 403,
+ /* 1160 */ 402, 1400, 920, 1204, 1176, 1177, 1178, 919, 1206, 291,
+ /* 1170 */ 1306, 1249, 412, 57, 57, 488, 1205, 563, 556, 412,
+ /* 1180 */ 1176, 1344, 123, 113, 1200, 1200, 1035, 1038, 1028, 1028,
+ /* 1190 */ 120, 120, 121, 121, 121, 121, 1400, 1143, 563, 44,
+ /* 1200 */ 44, 1207, 194, 1207, 273, 273, 1400, 461, 537, 1154,
+ /* 1210 */ 1143, 108, 555, 1143, 4, 391, 1121, 560, 1538, 335,
+ /* 1220 */ 58, 58, 6, 1246, 1099, 380, 1400, 376, 558, 1536,
+ /* 1230 */ 563, 422, 1221, 6, 304, 554, 1176, 1177, 1178, 1100,
+ /* 1240 */ 119, 119, 119, 119, 118, 118, 117, 117, 117, 116,
+ /* 1250 */ 441, 442, 59, 59, 1101, 516, 1535, 273, 273, 563,
+ /* 1260 */ 6, 563, 110, 552, 563, 528, 423, 413, 169, 548,
+ /* 1270 */ 560, 108, 555, 137, 4, 551, 484, 272, 215, 222,
+ /* 1280 */ 211, 60, 60, 61, 61, 98, 62, 62, 558, 273,
+ /* 1290 */ 273, 563, 1015, 467, 1221, 563, 434, 563, 106, 106,
+ /* 1300 */ 8, 920, 560, 273, 273, 107, 919, 442, 565, 564,
+ /* 1310 */ 563, 442, 1005, 45, 45, 464, 560, 46, 46, 47,
+ /* 1320 */ 47, 84, 202, 552, 1215, 404, 468, 563, 205, 304,
+ /* 1330 */ 554, 563, 49, 49, 563, 522, 404, 532, 563, 867,
+ /* 1340 */ 563, 105, 531, 103, 1005, 1005, 1007, 1008, 27, 50,
+ /* 1350 */ 50, 563, 1015, 63, 63, 475, 64, 64, 106, 106,
+ /* 1360 */ 65, 65, 14, 14, 17, 107, 563, 442, 565, 564,
+ /* 1370 */ 563, 303, 1005, 66, 66, 563, 226, 563, 959, 563,
+ /* 1380 */ 543, 404, 1196, 1343, 871, 278, 960, 456, 128, 128,
+ /* 1390 */ 563, 1065, 67, 67, 563, 206, 867, 52, 52, 68,
+ /* 1400 */ 68, 69, 69, 417, 1005, 1005, 1007, 1008, 27, 1563,
+ /* 1410 */ 1165, 444, 53, 53, 277, 1519, 156, 156, 307, 389,
+ /* 1420 */ 389, 388, 262, 386, 1165, 444, 839, 321, 277, 108,
+ /* 1430 */ 555, 523, 4, 389, 389, 388, 262, 386, 563, 223,
+ /* 1440 */ 839, 311, 326, 1492, 1117, 98, 558, 393, 1065, 310,
+ /* 1450 */ 563, 476, 563, 223, 563, 311, 879, 878, 1009, 277,
+ /* 1460 */ 157, 157, 463, 310, 389, 389, 388, 262, 386, 442,
+ /* 1470 */ 518, 839, 76, 76, 54, 54, 72, 72, 355, 225,
+ /* 1480 */ 563, 552, 275, 563, 223, 325, 311, 161, 354, 465,
+ /* 1490 */ 135, 563, 228, 225, 310, 532, 563, 206, 886, 887,
+ /* 1500 */ 533, 161, 129, 129, 135, 73, 73, 224, 962, 963,
+ /* 1510 */ 1015, 563, 287, 130, 130, 1009, 106, 106, 131, 131,
+ /* 1520 */ 563, 224, 563, 107, 225, 442, 565, 564, 997, 1276,
+ /* 1530 */ 1005, 250, 161, 127, 127, 135, 108, 555, 1077, 4,
+ /* 1540 */ 1077, 407, 155, 155, 154, 154, 304, 554, 1126, 563,
+ /* 1550 */ 1331, 563, 224, 558, 470, 407, 563, 250, 563, 1491,
+ /* 1560 */ 304, 554, 1005, 1005, 1007, 1008, 27, 563, 480, 332,
+ /* 1570 */ 448, 136, 136, 134, 134, 1340, 442, 336, 132, 132,
+ /* 1580 */ 133, 133, 563, 1076, 448, 1076, 407, 563, 552, 75,
+ /* 1590 */ 75, 304, 554, 339, 341, 343, 108, 555, 563, 4,
+ /* 1600 */ 1577, 299, 532, 563, 77, 77, 1291, 531, 472, 74,
+ /* 1610 */ 74, 250, 1275, 558, 350, 448, 331, 1015, 360, 98,
+ /* 1620 */ 42, 42, 1352, 106, 106, 48, 48, 1399, 494, 1327,
+ /* 1630 */ 107, 247, 442, 565, 564, 345, 442, 1005, 98, 1061,
+ /* 1640 */ 953, 917, 247, 250, 110, 1552, 550, 850, 552, 918,
+ /* 1650 */ 144, 1338, 110, 549, 1405, 1256, 1248, 1237, 1236, 1238,
+ /* 1660 */ 1571, 1324, 208, 390, 489, 265, 363, 200, 365, 1005,
+ /* 1670 */ 1005, 1007, 1008, 27, 11, 280, 221, 1015, 323, 474,
+ /* 1680 */ 1274, 367, 212, 106, 106, 924, 1386, 324, 288, 1381,
+ /* 1690 */ 107, 453, 442, 565, 564, 283, 329, 1005, 1391, 499,
+ /* 1700 */ 353, 1374, 1464, 108, 555, 1463, 4, 1574, 1390, 397,
+ /* 1710 */ 1215, 171, 254, 369, 383, 207, 195, 196, 1511, 553,
+ /* 1720 */ 558, 1509, 415, 1212, 100, 555, 83, 4, 204, 1005,
+ /* 1730 */ 1005, 1007, 1008, 27, 180, 166, 173, 219, 79, 82,
+ /* 1740 */ 458, 558, 175, 442, 35, 1387, 176, 459, 177, 178,
+ /* 1750 */ 492, 231, 96, 1469, 395, 552, 1393, 1392, 36, 466,
+ /* 1760 */ 1395, 184, 398, 481, 442, 1458, 235, 89, 1480, 487,
+ /* 1770 */ 266, 334, 237, 188, 490, 400, 552, 338, 238, 508,
+ /* 1780 */ 1239, 239, 1294, 1293, 1015, 1292, 1285, 429, 91, 871,
+ /* 1790 */ 106, 106, 1588, 213, 401, 1587, 431, 107, 1264, 442,
+ /* 1800 */ 565, 564, 1263, 352, 1005, 1015, 1262, 1586, 1557, 517,
+ /* 1810 */ 432, 106, 106, 1284, 297, 298, 358, 524, 107, 1335,
+ /* 1820 */ 442, 565, 564, 95, 1336, 1005, 252, 253, 435, 125,
+ /* 1830 */ 543, 1543, 10, 1444, 377, 1542, 1005, 1005, 1007, 1008,
+ /* 1840 */ 27, 97, 527, 375, 362, 102, 260, 364, 381, 1317,
+ /* 1850 */ 382, 1334, 366, 1245, 1333, 1316, 368, 1005, 1005, 1007,
+ /* 1860 */ 1008, 27, 1359, 1358, 34, 199, 1171, 566, 261, 263,
+ /* 1870 */ 264, 567, 1234, 158, 1229, 141, 295, 159, 1496, 302,
+ /* 1880 */ 1497, 1495, 1494, 160, 826, 209, 443, 201, 306, 210,
+ /* 1890 */ 78, 220, 1075, 138, 1073, 314, 162, 172, 1196, 227,
+ /* 1900 */ 174, 903, 322, 230, 1089, 179, 163, 164, 418, 408,
+ /* 1910 */ 409, 170, 181, 85, 86, 420, 87, 165, 1092, 88,
+ /* 1920 */ 233, 232, 1088, 151, 18, 234, 1081, 250, 333, 1209,
+ /* 1930 */ 185, 486, 236, 186, 37, 841, 491, 354, 240, 346,
+ /* 1940 */ 495, 187, 90, 869, 19, 20, 500, 503, 349, 92,
+ /* 1950 */ 167, 152, 296, 882, 93, 510, 94, 1159, 153, 1041,
+ /* 1960 */ 1128, 39, 214, 269, 1127, 271, 249, 952, 190, 947,
+ /* 1970 */ 110, 1149, 21, 7, 1153, 22, 1145, 23, 1147, 24,
+ /* 1980 */ 1133, 25, 1152, 33, 539, 193, 26, 1056, 98, 1042,
+ /* 1990 */ 1040, 1044, 1098, 1045, 1097, 256, 255, 28, 40, 257,
+ /* 2000 */ 1010, 851, 109, 29, 913, 559, 384, 387, 258, 1167,
+ /* 2010 */ 1166, 1225, 1225, 1225, 1579, 1225, 1225, 1225, 1225, 1225,
+ /* 2020 */ 1225, 1225, 1578,
};
static const YYCODETYPE yy_lookahead[] = {
- /* 0 */ 189, 211, 189, 189, 218, 189, 220, 189, 267, 268,
- /* 10 */ 269, 189, 210, 189, 228, 189, 267, 268, 269, 19,
- /* 20 */ 218, 189, 211, 212, 211, 212, 211, 211, 212, 211,
- /* 30 */ 212, 31, 211, 211, 212, 211, 212, 288, 300, 39,
- /* 40 */ 21, 189, 304, 43, 44, 45, 46, 47, 48, 49,
- /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 225, 19,
- /* 60 */ 189, 183, 184, 185, 186, 189, 248, 263, 236, 191,
- /* 70 */ 248, 193, 248, 197, 208, 257, 262, 201, 200, 257,
- /* 80 */ 200, 257, 81, 43, 44, 45, 46, 47, 48, 49,
- /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 189, 80,
- /* 100 */ 189, 101, 102, 103, 104, 105, 106, 107, 108, 109,
- /* 110 */ 110, 111, 234, 235, 234, 235, 305, 306, 305, 118,
- /* 120 */ 307, 305, 306, 297, 298, 247, 86, 247, 88, 19,
- /* 130 */ 259, 251, 252, 267, 268, 269, 26, 136, 137, 261,
- /* 140 */ 121, 101, 102, 103, 104, 105, 106, 107, 108, 109,
- /* 150 */ 110, 111, 59, 43, 44, 45, 46, 47, 48, 49,
- /* 160 */ 50, 51, 52, 53, 54, 55, 56, 57, 259, 291,
- /* 170 */ 105, 106, 107, 108, 109, 110, 111, 158, 189, 69,
- /* 180 */ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
- /* 190 */ 111, 107, 108, 109, 110, 111, 205, 206, 207, 19,
- /* 200 */ 19, 54, 55, 56, 57, 58, 29, 114, 115, 116,
- /* 210 */ 33, 101, 102, 103, 104, 105, 106, 107, 108, 109,
- /* 220 */ 110, 111, 233, 43, 44, 45, 46, 47, 48, 49,
- /* 230 */ 50, 51, 52, 53, 54, 55, 56, 57, 19, 126,
- /* 240 */ 127, 148, 65, 24, 214, 200, 59, 67, 101, 102,
- /* 250 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 22,
- /* 260 */ 189, 111, 43, 44, 45, 46, 47, 48, 49, 50,
- /* 270 */ 51, 52, 53, 54, 55, 56, 57, 206, 207, 234,
- /* 280 */ 235, 101, 102, 103, 104, 105, 106, 107, 108, 109,
- /* 290 */ 110, 111, 247, 76, 107, 114, 59, 267, 268, 269,
- /* 300 */ 189, 114, 115, 116, 162, 163, 89, 19, 263, 92,
- /* 310 */ 189, 23, 54, 55, 56, 57, 189, 206, 207, 22,
- /* 320 */ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
- /* 330 */ 111, 43, 44, 45, 46, 47, 48, 49, 50, 51,
- /* 340 */ 52, 53, 54, 55, 56, 57, 19, 189, 277, 59,
- /* 350 */ 23, 114, 115, 116, 46, 47, 48, 49, 61, 101,
- /* 360 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
- /* 370 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- /* 380 */ 53, 54, 55, 56, 57, 125, 126, 127, 277, 101,
- /* 390 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
- /* 400 */ 59, 189, 189, 276, 114, 115, 116, 117, 73, 59,
- /* 410 */ 120, 121, 122, 72, 214, 19, 81, 259, 19, 23,
- /* 420 */ 130, 81, 72, 24, 211, 212, 221, 119, 101, 102,
- /* 430 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 43,
+ /* 0 */ 192, 221, 192, 223, 192, 214, 272, 273, 274, 217,
+ /* 10 */ 192, 231, 217, 192, 192, 192, 272, 273, 274, 19,
+ /* 20 */ 233, 234, 214, 215, 214, 215, 203, 293, 203, 233,
+ /* 30 */ 234, 31, 214, 215, 214, 214, 215, 214, 215, 39,
+ /* 40 */ 208, 209, 210, 43, 44, 45, 46, 47, 48, 49,
+ /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 236, 19,
+ /* 60 */ 237, 238, 237, 238, 272, 273, 274, 272, 273, 274,
+ /* 70 */ 192, 211, 251, 250, 251, 250, 26, 192, 200, 254,
+ /* 80 */ 255, 260, 204, 43, 44, 45, 46, 47, 48, 49,
+ /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 192, 214,
+ /* 100 */ 215, 214, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 110 */ 110, 111, 112, 59, 229, 192, 294, 16, 306, 307,
+ /* 120 */ 312, 313, 312, 311, 314, 59, 86, 204, 88, 19,
+ /* 130 */ 312, 313, 272, 273, 274, 271, 26, 22, 54, 55,
+ /* 140 */ 56, 57, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 150 */ 110, 111, 112, 43, 44, 45, 46, 47, 48, 49,
+ /* 160 */ 50, 51, 52, 53, 54, 55, 56, 57, 53, 115,
+ /* 170 */ 116, 117, 118, 309, 310, 121, 122, 123, 77, 69,
+ /* 180 */ 79, 115, 116, 117, 59, 131, 102, 103, 104, 105,
+ /* 190 */ 106, 107, 108, 109, 110, 111, 112, 72, 148, 19,
+ /* 200 */ 54, 55, 56, 57, 58, 108, 109, 110, 111, 112,
+ /* 210 */ 304, 305, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 220 */ 110, 111, 112, 43, 44, 45, 46, 47, 48, 49,
+ /* 230 */ 50, 51, 52, 53, 54, 55, 56, 57, 19, 112,
+ /* 240 */ 115, 116, 117, 24, 208, 209, 210, 67, 102, 103,
+ /* 250 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 192,
+ /* 260 */ 59, 160, 43, 44, 45, 46, 47, 48, 49, 50,
+ /* 270 */ 51, 52, 53, 54, 55, 56, 57, 19, 46, 47,
+ /* 280 */ 48, 49, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 290 */ 110, 111, 112, 213, 73, 184, 185, 186, 187, 188,
+ /* 300 */ 189, 221, 81, 236, 46, 194, 192, 196, 19, 59,
+ /* 310 */ 133, 59, 135, 136, 203, 192, 115, 116, 117, 127,
+ /* 320 */ 128, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+ /* 330 */ 111, 112, 43, 44, 45, 46, 47, 48, 49, 50,
+ /* 340 */ 51, 52, 53, 54, 55, 56, 57, 126, 237, 238,
+ /* 350 */ 100, 150, 120, 230, 186, 187, 188, 189, 137, 138,
+ /* 360 */ 108, 250, 194, 26, 196, 115, 116, 115, 116, 117,
+ /* 370 */ 120, 203, 114, 164, 165, 264, 102, 103, 104, 105,
+ /* 380 */ 106, 107, 108, 109, 110, 111, 112, 192, 130, 111,
+ /* 390 */ 112, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+ /* 400 */ 111, 112, 152, 153, 154, 237, 238, 296, 192, 214,
+ /* 410 */ 215, 228, 192, 307, 192, 19, 59, 311, 250, 23,
+ /* 420 */ 22, 106, 107, 108, 109, 110, 111, 112, 192, 72,
+ /* 430 */ 214, 215, 264, 192, 214, 215, 214, 215, 149, 43,
/* 440 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
- /* 450 */ 54, 55, 56, 57, 19, 114, 115, 116, 23, 208,
- /* 460 */ 125, 248, 189, 189, 114, 115, 116, 267, 268, 269,
- /* 470 */ 189, 136, 137, 189, 262, 22, 136, 137, 43, 44,
- /* 480 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
- /* 490 */ 55, 56, 57, 189, 95, 211, 212, 101, 102, 103,
- /* 500 */ 104, 105, 106, 107, 108, 109, 110, 111, 59, 189,
- /* 510 */ 111, 189, 59, 76, 294, 295, 117, 118, 119, 120,
- /* 520 */ 121, 122, 123, 19, 87, 189, 89, 23, 129, 92,
- /* 530 */ 279, 227, 248, 22, 189, 284, 101, 102, 103, 104,
- /* 540 */ 105, 106, 107, 108, 109, 110, 111, 43, 44, 45,
- /* 550 */ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
- /* 560 */ 56, 57, 19, 114, 115, 116, 23, 114, 115, 116,
- /* 570 */ 59, 117, 299, 300, 120, 121, 122, 304, 189, 189,
- /* 580 */ 143, 189, 110, 111, 130, 22, 43, 44, 45, 46,
- /* 590 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
- /* 600 */ 57, 211, 212, 211, 212, 101, 102, 103, 104, 105,
- /* 610 */ 106, 107, 108, 109, 110, 111, 226, 189, 226, 189,
- /* 620 */ 298, 132, 59, 134, 135, 114, 115, 116, 189, 59,
- /* 630 */ 285, 19, 7, 8, 9, 23, 205, 206, 207, 211,
- /* 640 */ 212, 211, 212, 221, 101, 102, 103, 104, 105, 106,
- /* 650 */ 107, 108, 109, 110, 111, 43, 44, 45, 46, 47,
- /* 660 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
- /* 670 */ 19, 181, 182, 183, 184, 185, 186, 114, 115, 116,
- /* 680 */ 189, 191, 133, 193, 114, 115, 116, 138, 299, 300,
- /* 690 */ 200, 22, 201, 304, 43, 44, 45, 46, 47, 48,
- /* 700 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 35,
- /* 710 */ 189, 141, 189, 101, 102, 103, 104, 105, 106, 107,
- /* 720 */ 108, 109, 110, 111, 234, 235, 22, 23, 59, 184,
- /* 730 */ 26, 186, 211, 212, 211, 212, 191, 247, 193, 19,
- /* 740 */ 66, 105, 106, 73, 189, 200, 189, 226, 74, 226,
- /* 750 */ 22, 261, 101, 102, 103, 104, 105, 106, 107, 108,
- /* 760 */ 109, 110, 111, 43, 44, 45, 46, 47, 48, 49,
- /* 770 */ 50, 51, 52, 53, 54, 55, 56, 57, 189, 234,
- /* 780 */ 235, 291, 19, 114, 115, 116, 150, 59, 152, 189,
- /* 790 */ 233, 236, 247, 59, 189, 125, 126, 127, 59, 300,
- /* 800 */ 211, 212, 128, 304, 100, 19, 261, 156, 45, 46,
- /* 810 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
- /* 820 */ 57, 101, 102, 103, 104, 105, 106, 107, 108, 109,
- /* 830 */ 110, 111, 46, 233, 189, 189, 291, 248, 99, 189,
- /* 840 */ 125, 126, 127, 115, 26, 200, 289, 230, 231, 115,
- /* 850 */ 200, 16, 189, 114, 115, 189, 211, 212, 119, 221,
- /* 860 */ 189, 211, 212, 258, 101, 102, 103, 104, 105, 106,
- /* 870 */ 107, 108, 109, 110, 111, 189, 156, 211, 212, 234,
- /* 880 */ 235, 189, 211, 212, 234, 235, 22, 201, 189, 150,
- /* 890 */ 151, 152, 247, 248, 76, 16, 19, 247, 248, 113,
- /* 900 */ 189, 24, 257, 211, 212, 189, 26, 89, 262, 223,
- /* 910 */ 92, 225, 77, 189, 79, 129, 19, 53, 226, 248,
- /* 920 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- /* 930 */ 53, 54, 55, 56, 57, 236, 19, 271, 189, 99,
- /* 940 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- /* 950 */ 53, 54, 55, 56, 57, 115, 77, 59, 79, 119,
- /* 960 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- /* 970 */ 53, 54, 55, 56, 57, 259, 22, 23, 101, 102,
- /* 980 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 59,
- /* 990 */ 150, 151, 152, 158, 22, 244, 24, 246, 101, 102,
- /* 1000 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 285,
- /* 1010 */ 189, 189, 114, 115, 116, 200, 136, 137, 101, 102,
- /* 1020 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 230,
- /* 1030 */ 231, 59, 211, 212, 285, 105, 106, 189, 19, 141,
- /* 1040 */ 234, 235, 239, 113, 114, 115, 116, 226, 118, 234,
- /* 1050 */ 235, 189, 249, 247, 100, 189, 126, 23, 236, 107,
- /* 1060 */ 26, 189, 247, 44, 45, 46, 47, 48, 49, 50,
- /* 1070 */ 51, 52, 53, 54, 55, 56, 57, 211, 212, 59,
- /* 1080 */ 150, 233, 152, 211, 212, 133, 12, 115, 189, 189,
- /* 1090 */ 138, 19, 20, 300, 22, 233, 76, 304, 226, 11,
- /* 1100 */ 208, 27, 22, 23, 200, 19, 26, 87, 36, 89,
- /* 1110 */ 211, 212, 92, 300, 248, 189, 42, 304, 189, 250,
- /* 1120 */ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
- /* 1130 */ 111, 59, 200, 233, 114, 115, 116, 63, 234, 235,
- /* 1140 */ 235, 19, 20, 71, 22, 300, 189, 73, 200, 304,
- /* 1150 */ 116, 247, 247, 81, 189, 200, 227, 26, 36, 234,
- /* 1160 */ 235, 203, 204, 143, 200, 26, 234, 235, 194, 200,
- /* 1170 */ 48, 99, 247, 66, 189, 141, 284, 105, 106, 247,
- /* 1180 */ 100, 59, 234, 235, 112, 259, 114, 115, 116, 234,
- /* 1190 */ 235, 119, 85, 71, 266, 247, 211, 212, 234, 235,
- /* 1200 */ 114, 94, 247, 234, 235, 12, 266, 85, 136, 137,
- /* 1210 */ 189, 247, 90, 26, 126, 127, 247, 189, 26, 22,
- /* 1220 */ 27, 99, 150, 151, 152, 153, 154, 105, 106, 189,
- /* 1230 */ 302, 303, 211, 212, 112, 42, 114, 115, 116, 211,
- /* 1240 */ 212, 119, 302, 303, 19, 20, 189, 22, 274, 189,
- /* 1250 */ 15, 144, 278, 189, 22, 23, 63, 189, 189, 203,
- /* 1260 */ 204, 36, 136, 137, 155, 24, 157, 143, 211, 212,
- /* 1270 */ 189, 140, 150, 151, 152, 153, 154, 0, 1, 2,
- /* 1280 */ 211, 212, 5, 46, 59, 161, 147, 10, 11, 12,
- /* 1290 */ 13, 14, 211, 212, 17, 60, 71, 189, 258, 189,
- /* 1300 */ 59, 189, 105, 106, 189, 189, 189, 30, 116, 32,
- /* 1310 */ 85, 124, 189, 251, 252, 90, 189, 40, 258, 211,
- /* 1320 */ 212, 211, 212, 189, 99, 26, 211, 212, 211, 212,
- /* 1330 */ 105, 106, 100, 141, 211, 212, 119, 112, 189, 114,
- /* 1340 */ 115, 116, 23, 189, 119, 26, 129, 70, 189, 31,
- /* 1350 */ 113, 19, 20, 24, 22, 78, 115, 39, 81, 189,
- /* 1360 */ 211, 212, 26, 189, 22, 211, 212, 189, 36, 189,
- /* 1370 */ 211, 212, 189, 189, 97, 150, 151, 152, 153, 154,
- /* 1380 */ 127, 211, 212, 189, 189, 211, 212, 189, 189, 211,
- /* 1390 */ 212, 59, 189, 189, 211, 212, 23, 189, 22, 26,
- /* 1400 */ 24, 189, 149, 71, 189, 211, 212, 189, 131, 211,
- /* 1410 */ 212, 189, 189, 136, 137, 211, 212, 85, 189, 211,
- /* 1420 */ 212, 59, 90, 211, 212, 292, 293, 118, 119, 211,
- /* 1430 */ 212, 99, 23, 211, 212, 26, 159, 105, 106, 189,
- /* 1440 */ 211, 212, 143, 150, 112, 152, 114, 115, 116, 1,
- /* 1450 */ 2, 119, 23, 5, 23, 26, 189, 26, 10, 11,
- /* 1460 */ 12, 13, 14, 83, 84, 17, 253, 189, 139, 189,
- /* 1470 */ 19, 20, 189, 22, 189, 189, 140, 115, 30, 59,
- /* 1480 */ 32, 139, 150, 151, 152, 153, 154, 36, 40, 211,
- /* 1490 */ 212, 211, 212, 59, 211, 212, 211, 212, 7, 8,
- /* 1500 */ 19, 20, 189, 22, 150, 189, 152, 231, 281, 189,
- /* 1510 */ 59, 189, 23, 189, 189, 26, 189, 36, 70, 189,
- /* 1520 */ 23, 237, 71, 26, 211, 212, 78, 211, 212, 81,
- /* 1530 */ 189, 211, 212, 211, 212, 115, 211, 212, 211, 212,
- /* 1540 */ 59, 211, 212, 23, 23, 97, 26, 26, 23, 115,
- /* 1550 */ 99, 26, 71, 189, 189, 189, 105, 106, 107, 23,
- /* 1560 */ 189, 23, 26, 112, 26, 114, 115, 116, 189, 309,
- /* 1570 */ 119, 23, 19, 20, 26, 22, 189, 211, 212, 131,
- /* 1580 */ 99, 189, 211, 212, 136, 137, 105, 106, 189, 36,
- /* 1590 */ 211, 212, 189, 112, 189, 114, 115, 116, 211, 212,
- /* 1600 */ 119, 150, 151, 152, 153, 154, 189, 159, 23, 250,
- /* 1610 */ 189, 26, 59, 189, 189, 189, 189, 189, 280, 189,
- /* 1620 */ 250, 189, 189, 238, 71, 189, 189, 250, 211, 212,
- /* 1630 */ 187, 150, 151, 152, 153, 154, 211, 212, 250, 290,
- /* 1640 */ 240, 211, 212, 211, 212, 254, 286, 209, 254, 241,
- /* 1650 */ 240, 254, 99, 286, 215, 220, 214, 244, 105, 106,
- /* 1660 */ 214, 214, 244, 273, 224, 112, 192, 114, 115, 116,
- /* 1670 */ 60, 290, 119, 5, 139, 196, 196, 38, 10, 11,
- /* 1680 */ 12, 13, 14, 238, 240, 17, 196, 148, 287, 287,
- /* 1690 */ 276, 113, 22, 147, 241, 43, 229, 241, 30, 18,
- /* 1700 */ 32, 232, 232, 150, 151, 152, 153, 154, 40, 232,
- /* 1710 */ 232, 196, 18, 195, 265, 265, 264, 241, 264, 196,
- /* 1720 */ 155, 229, 229, 241, 241, 241, 195, 62, 196, 195,
- /* 1730 */ 22, 113, 216, 196, 222, 195, 195, 282, 70, 196,
- /* 1740 */ 283, 213, 216, 213, 64, 22, 78, 124, 219, 81,
- /* 1750 */ 162, 111, 219, 142, 256, 213, 113, 255, 213, 256,
- /* 1760 */ 216, 303, 215, 213, 213, 97, 255, 213, 216, 275,
- /* 1770 */ 275, 222, 216, 256, 255, 196, 91, 82, 256, 255,
- /* 1780 */ 308, 308, 146, 22, 143, 196, 155, 260, 25, 145,
- /* 1790 */ 144, 199, 26, 198, 13, 190, 190, 6, 293, 131,
- /* 1800 */ 188, 188, 245, 244, 136, 137, 245, 243, 242, 241,
- /* 1810 */ 188, 202, 208, 217, 217, 260, 208, 4, 202, 3,
- /* 1820 */ 22, 202, 208, 208, 160, 15, 209, 159, 270, 209,
- /* 1830 */ 98, 16, 272, 208, 23, 23, 137, 148, 128, 20,
- /* 1840 */ 140, 24, 16, 142, 1, 140, 149, 128, 61, 53,
- /* 1850 */ 148, 37, 53, 53, 53, 128, 114, 34, 296, 296,
- /* 1860 */ 139, 1, 5, 22, 113, 158, 26, 75, 41, 139,
- /* 1870 */ 68, 68, 113, 24, 20, 19, 129, 123, 23, 96,
- /* 1880 */ 22, 22, 37, 22, 22, 67, 22, 67, 59, 24,
- /* 1890 */ 23, 28, 67, 147, 22, 26, 23, 23, 23, 23,
- /* 1900 */ 22, 24, 23, 22, 24, 23, 139, 23, 114, 22,
- /* 1910 */ 141, 26, 88, 75, 86, 44, 23, 34, 22, 75,
- /* 1920 */ 34, 24, 34, 34, 34, 93, 34, 26, 26, 34,
- /* 1930 */ 23, 23, 23, 23, 23, 11, 23, 22, 26, 22,
- /* 1940 */ 22, 133, 23, 23, 22, 22, 139, 26, 139, 23,
- /* 1950 */ 15, 1, 1, 310, 310, 310, 310, 310, 310, 310,
- /* 1960 */ 139, 139, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 1970 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 1980 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 1990 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2000 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2010 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2020 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2030 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2040 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2050 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2060 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2070 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2080 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2090 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2100 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2110 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2120 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2130 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
- /* 2140 */ 310, 310, 310,
+ /* 450 */ 54, 55, 56, 57, 117, 214, 215, 59, 19, 187,
+ /* 460 */ 192, 189, 23, 81, 296, 192, 194, 251, 196, 59,
+ /* 470 */ 229, 251, 115, 116, 117, 203, 260, 106, 107, 142,
+ /* 480 */ 260, 267, 43, 44, 45, 46, 47, 48, 49, 50,
+ /* 490 */ 51, 52, 53, 54, 55, 56, 57, 261, 102, 103,
+ /* 500 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 237,
+ /* 510 */ 238, 76, 192, 115, 116, 117, 144, 192, 76, 137,
+ /* 520 */ 138, 192, 250, 152, 89, 154, 116, 92, 19, 87,
+ /* 530 */ 262, 89, 23, 22, 92, 163, 264, 192, 22, 214,
+ /* 540 */ 215, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+ /* 550 */ 111, 112, 43, 44, 45, 46, 47, 48, 49, 50,
+ /* 560 */ 51, 52, 53, 54, 55, 56, 57, 19, 296, 118,
+ /* 570 */ 59, 23, 121, 122, 123, 59, 251, 26, 46, 306,
+ /* 580 */ 307, 261, 131, 192, 311, 192, 144, 192, 22, 203,
+ /* 590 */ 100, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ /* 600 */ 52, 53, 54, 55, 56, 57, 116, 214, 215, 271,
+ /* 610 */ 120, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+ /* 620 */ 111, 112, 229, 237, 238, 59, 115, 116, 117, 299,
+ /* 630 */ 300, 115, 116, 117, 59, 16, 250, 19, 192, 192,
+ /* 640 */ 19, 23, 152, 153, 154, 24, 114, 309, 310, 192,
+ /* 650 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ /* 660 */ 112, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ /* 670 */ 52, 53, 54, 55, 56, 57, 19, 7, 8, 9,
+ /* 680 */ 23, 115, 116, 117, 203, 290, 239, 238, 137, 138,
+ /* 690 */ 115, 116, 117, 236, 192, 22, 77, 81, 79, 250,
+ /* 700 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ /* 710 */ 53, 54, 55, 56, 57, 192, 95, 142, 237, 238,
+ /* 720 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ /* 730 */ 112, 250, 59, 112, 192, 119, 26, 214, 215, 118,
+ /* 740 */ 119, 120, 121, 122, 123, 124, 19, 192, 267, 302,
+ /* 750 */ 23, 130, 229, 137, 138, 23, 214, 215, 26, 102,
+ /* 760 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
+ /* 770 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ /* 780 */ 53, 54, 55, 56, 57, 19, 76, 11, 115, 116,
+ /* 790 */ 117, 192, 29, 251, 239, 73, 33, 192, 192, 89,
+ /* 800 */ 192, 192, 92, 192, 192, 126, 127, 128, 224, 43,
+ /* 810 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+ /* 820 */ 54, 55, 56, 57, 192, 35, 214, 215, 65, 102,
+ /* 830 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
+ /* 840 */ 59, 229, 192, 192, 239, 239, 214, 215, 126, 127,
+ /* 850 */ 128, 126, 127, 128, 307, 19, 66, 302, 311, 192,
+ /* 860 */ 261, 229, 224, 22, 74, 214, 215, 192, 102, 103,
+ /* 870 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 43,
+ /* 880 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+ /* 890 */ 54, 55, 56, 57, 192, 192, 115, 116, 117, 19,
+ /* 900 */ 59, 290, 251, 127, 128, 192, 23, 302, 302, 26,
+ /* 910 */ 302, 236, 192, 22, 21, 24, 214, 215, 192, 129,
+ /* 920 */ 22, 192, 24, 142, 158, 45, 46, 47, 48, 49,
+ /* 930 */ 50, 51, 52, 53, 54, 55, 56, 57, 102, 103,
+ /* 940 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 192,
+ /* 950 */ 59, 12, 192, 251, 192, 305, 192, 116, 22, 23,
+ /* 960 */ 242, 203, 26, 203, 24, 236, 27, 237, 238, 266,
+ /* 970 */ 252, 214, 215, 80, 214, 215, 214, 215, 214, 215,
+ /* 980 */ 250, 42, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 990 */ 110, 111, 112, 229, 158, 237, 238, 237, 238, 59,
+ /* 1000 */ 117, 281, 63, 192, 192, 192, 192, 116, 250, 192,
+ /* 1010 */ 250, 251, 73, 251, 19, 122, 290, 237, 238, 24,
+ /* 1020 */ 260, 209, 210, 209, 210, 142, 242, 214, 215, 197,
+ /* 1030 */ 250, 22, 23, 276, 19, 26, 252, 101, 43, 44,
+ /* 1040 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ /* 1050 */ 55, 56, 57, 160, 19, 211, 116, 192, 43, 44,
+ /* 1060 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ /* 1070 */ 55, 56, 57, 192, 192, 22, 192, 266, 43, 44,
+ /* 1080 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ /* 1090 */ 55, 56, 57, 192, 282, 192, 282, 102, 103, 104,
+ /* 1100 */ 105, 106, 107, 108, 109, 110, 111, 112, 59, 192,
+ /* 1110 */ 101, 279, 192, 192, 230, 283, 192, 102, 103, 104,
+ /* 1120 */ 105, 106, 107, 108, 109, 110, 111, 112, 204, 211,
+ /* 1130 */ 66, 214, 215, 289, 214, 215, 108, 102, 103, 104,
+ /* 1140 */ 105, 106, 107, 108, 109, 110, 111, 112, 266, 85,
+ /* 1150 */ 226, 192, 228, 22, 23, 106, 107, 19, 94, 106,
+ /* 1160 */ 107, 192, 134, 114, 115, 116, 117, 139, 119, 266,
+ /* 1170 */ 203, 206, 207, 214, 215, 192, 127, 192, 206, 207,
+ /* 1180 */ 59, 192, 44, 45, 46, 47, 48, 49, 50, 51,
+ /* 1190 */ 52, 53, 54, 55, 56, 57, 192, 76, 192, 214,
+ /* 1200 */ 215, 152, 284, 154, 237, 238, 192, 289, 87, 145,
+ /* 1210 */ 89, 19, 20, 92, 22, 22, 23, 250, 307, 236,
+ /* 1220 */ 214, 215, 311, 203, 12, 247, 192, 249, 36, 307,
+ /* 1230 */ 192, 262, 101, 311, 137, 138, 115, 116, 117, 27,
+ /* 1240 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ /* 1250 */ 112, 59, 214, 215, 42, 203, 307, 237, 238, 192,
+ /* 1260 */ 311, 192, 26, 71, 192, 144, 262, 297, 298, 203,
+ /* 1270 */ 250, 19, 20, 81, 22, 63, 262, 254, 255, 15,
+ /* 1280 */ 26, 214, 215, 214, 215, 26, 214, 215, 36, 237,
+ /* 1290 */ 238, 192, 100, 114, 101, 192, 262, 192, 106, 107,
+ /* 1300 */ 48, 134, 250, 237, 238, 113, 139, 115, 116, 117,
+ /* 1310 */ 192, 59, 120, 214, 215, 242, 250, 214, 215, 214,
+ /* 1320 */ 215, 148, 149, 71, 60, 252, 242, 192, 149, 137,
+ /* 1330 */ 138, 192, 214, 215, 192, 19, 252, 85, 192, 59,
+ /* 1340 */ 192, 157, 90, 159, 152, 153, 154, 155, 156, 214,
+ /* 1350 */ 215, 192, 100, 214, 215, 19, 214, 215, 106, 107,
+ /* 1360 */ 214, 215, 214, 215, 22, 113, 192, 115, 116, 117,
+ /* 1370 */ 192, 242, 120, 214, 215, 192, 24, 192, 31, 192,
+ /* 1380 */ 144, 252, 26, 192, 125, 99, 39, 192, 214, 215,
+ /* 1390 */ 192, 59, 214, 215, 192, 141, 116, 214, 215, 214,
+ /* 1400 */ 215, 214, 215, 61, 152, 153, 154, 155, 156, 0,
+ /* 1410 */ 1, 2, 214, 215, 5, 192, 214, 215, 132, 10,
+ /* 1420 */ 11, 12, 13, 14, 1, 2, 17, 192, 5, 19,
+ /* 1430 */ 20, 115, 22, 10, 11, 12, 13, 14, 192, 30,
+ /* 1440 */ 17, 32, 23, 192, 23, 26, 36, 26, 116, 40,
+ /* 1450 */ 192, 115, 192, 30, 192, 32, 119, 120, 59, 5,
+ /* 1460 */ 214, 215, 128, 40, 10, 11, 12, 13, 14, 59,
+ /* 1470 */ 19, 17, 214, 215, 214, 215, 214, 215, 120, 70,
+ /* 1480 */ 192, 71, 22, 192, 30, 151, 32, 78, 130, 128,
+ /* 1490 */ 81, 192, 140, 70, 40, 85, 192, 141, 7, 8,
+ /* 1500 */ 90, 78, 214, 215, 81, 214, 215, 98, 83, 84,
+ /* 1510 */ 100, 192, 151, 214, 215, 116, 106, 107, 214, 215,
+ /* 1520 */ 192, 98, 192, 113, 70, 115, 116, 117, 23, 224,
+ /* 1530 */ 120, 26, 78, 214, 215, 81, 19, 20, 152, 22,
+ /* 1540 */ 154, 132, 214, 215, 214, 215, 137, 138, 97, 192,
+ /* 1550 */ 256, 192, 98, 36, 23, 132, 192, 26, 192, 192,
+ /* 1560 */ 137, 138, 152, 153, 154, 155, 156, 192, 192, 192,
+ /* 1570 */ 161, 214, 215, 214, 215, 192, 59, 192, 214, 215,
+ /* 1580 */ 214, 215, 192, 152, 161, 154, 132, 192, 71, 214,
+ /* 1590 */ 215, 137, 138, 192, 192, 192, 19, 20, 192, 22,
+ /* 1600 */ 140, 253, 85, 192, 214, 215, 192, 90, 23, 214,
+ /* 1610 */ 215, 26, 192, 36, 192, 161, 23, 100, 192, 26,
+ /* 1620 */ 214, 215, 192, 106, 107, 214, 215, 192, 23, 192,
+ /* 1630 */ 113, 26, 115, 116, 117, 23, 59, 120, 26, 23,
+ /* 1640 */ 23, 23, 26, 26, 26, 316, 234, 23, 71, 23,
+ /* 1650 */ 26, 192, 26, 192, 192, 192, 192, 192, 192, 192,
+ /* 1660 */ 192, 253, 212, 190, 286, 285, 253, 240, 253, 152,
+ /* 1670 */ 153, 154, 155, 156, 241, 243, 295, 100, 291, 291,
+ /* 1680 */ 223, 253, 227, 106, 107, 108, 269, 244, 244, 265,
+ /* 1690 */ 113, 257, 115, 116, 117, 257, 243, 120, 269, 218,
+ /* 1700 */ 217, 265, 217, 19, 20, 217, 22, 195, 269, 269,
+ /* 1710 */ 60, 295, 140, 257, 243, 241, 247, 247, 199, 278,
+ /* 1720 */ 36, 199, 199, 38, 19, 20, 150, 22, 149, 152,
+ /* 1730 */ 153, 154, 155, 156, 22, 43, 232, 295, 292, 292,
+ /* 1740 */ 18, 36, 235, 59, 268, 270, 235, 199, 235, 235,
+ /* 1750 */ 18, 198, 148, 281, 244, 71, 270, 270, 268, 244,
+ /* 1760 */ 232, 232, 244, 199, 59, 244, 198, 157, 288, 62,
+ /* 1770 */ 199, 287, 198, 22, 219, 219, 71, 199, 198, 114,
+ /* 1780 */ 199, 198, 216, 216, 100, 216, 225, 64, 22, 125,
+ /* 1790 */ 106, 107, 222, 164, 219, 222, 24, 113, 216, 115,
+ /* 1800 */ 116, 117, 218, 216, 120, 100, 216, 216, 310, 303,
+ /* 1810 */ 112, 106, 107, 225, 280, 280, 219, 143, 113, 259,
+ /* 1820 */ 115, 116, 117, 114, 259, 120, 199, 91, 82, 147,
+ /* 1830 */ 144, 315, 22, 275, 199, 315, 152, 153, 154, 155,
+ /* 1840 */ 156, 146, 145, 247, 258, 157, 25, 258, 245, 248,
+ /* 1850 */ 244, 259, 258, 202, 259, 248, 258, 152, 153, 154,
+ /* 1860 */ 155, 156, 263, 263, 26, 246, 13, 201, 193, 193,
+ /* 1870 */ 6, 191, 191, 205, 191, 220, 220, 205, 211, 277,
+ /* 1880 */ 211, 211, 211, 205, 4, 212, 3, 22, 162, 212,
+ /* 1890 */ 211, 15, 23, 16, 23, 138, 129, 150, 26, 24,
+ /* 1900 */ 141, 20, 16, 143, 1, 141, 129, 129, 61, 301,
+ /* 1910 */ 301, 298, 150, 53, 53, 37, 53, 129, 115, 53,
+ /* 1920 */ 140, 34, 1, 5, 22, 114, 68, 26, 160, 75,
+ /* 1930 */ 68, 41, 140, 114, 24, 20, 19, 130, 124, 23,
+ /* 1940 */ 67, 22, 22, 59, 22, 22, 67, 96, 24, 22,
+ /* 1950 */ 37, 23, 67, 28, 148, 22, 26, 23, 23, 23,
+ /* 1960 */ 23, 22, 140, 23, 97, 23, 34, 115, 22, 142,
+ /* 1970 */ 26, 75, 34, 44, 75, 34, 88, 34, 86, 34,
+ /* 1980 */ 23, 34, 93, 22, 24, 26, 34, 23, 26, 23,
+ /* 1990 */ 23, 23, 23, 11, 23, 22, 26, 22, 22, 140,
+ /* 2000 */ 23, 23, 22, 22, 134, 26, 23, 15, 140, 1,
+ /* 2010 */ 1, 317, 317, 317, 140, 317, 317, 317, 317, 317,
+ /* 2020 */ 317, 317, 140, 317, 317, 317, 317, 317, 317, 317,
+ /* 2030 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2040 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2050 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2060 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2070 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2080 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2090 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2100 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2110 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2120 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2130 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2140 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2150 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2160 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2170 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2180 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2190 */ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ /* 2200 */ 317, 317, 317, 317, 317, 317, 317,
};
-#define YY_SHIFT_COUNT (552)
+#define YY_SHIFT_COUNT (569)
#define YY_SHIFT_MIN (0)
-#define YY_SHIFT_MAX (1951)
+#define YY_SHIFT_MAX (2009)
static const unsigned short int yy_shift_ofst[] = {
- /* 0 */ 1448, 1277, 1668, 1072, 1072, 340, 1122, 1225, 1332, 1481,
- /* 10 */ 1481, 1481, 335, 0, 0, 180, 897, 1481, 1481, 1481,
- /* 20 */ 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481,
- /* 30 */ 930, 930, 1020, 1020, 290, 1, 340, 340, 340, 340,
- /* 40 */ 340, 340, 40, 110, 219, 288, 327, 396, 435, 504,
- /* 50 */ 543, 612, 651, 720, 877, 897, 897, 897, 897, 897,
- /* 60 */ 897, 897, 897, 897, 897, 897, 897, 897, 897, 897,
- /* 70 */ 897, 897, 897, 917, 897, 1019, 763, 763, 1451, 1481,
- /* 80 */ 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481,
- /* 90 */ 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481,
- /* 100 */ 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481,
- /* 110 */ 1481, 1481, 1553, 1481, 1481, 1481, 1481, 1481, 1481, 1481,
- /* 120 */ 1481, 1481, 1481, 1481, 1481, 1481, 147, 258, 258, 258,
- /* 130 */ 258, 258, 79, 65, 84, 449, 19, 786, 449, 636,
- /* 140 */ 636, 449, 880, 880, 880, 880, 113, 142, 142, 472,
- /* 150 */ 150, 1962, 1962, 399, 399, 399, 93, 237, 341, 237,
- /* 160 */ 237, 1074, 1074, 437, 350, 704, 1080, 449, 449, 449,
- /* 170 */ 449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
- /* 180 */ 449, 449, 449, 449, 449, 449, 449, 449, 818, 818,
- /* 190 */ 449, 1088, 217, 217, 734, 734, 1124, 1126, 1962, 1962,
- /* 200 */ 1962, 739, 840, 840, 453, 454, 511, 187, 563, 570,
- /* 210 */ 898, 669, 449, 449, 449, 449, 449, 449, 449, 449,
- /* 220 */ 449, 670, 449, 449, 449, 449, 449, 449, 449, 449,
- /* 230 */ 449, 449, 449, 449, 674, 674, 674, 449, 449, 449,
- /* 240 */ 449, 1034, 449, 449, 449, 972, 1107, 449, 449, 1193,
- /* 250 */ 449, 449, 449, 449, 449, 449, 449, 449, 260, 177,
- /* 260 */ 489, 1241, 1241, 1241, 1241, 1192, 489, 489, 952, 1197,
- /* 270 */ 625, 1235, 1131, 181, 181, 1086, 1139, 1131, 1086, 1187,
- /* 280 */ 1319, 1237, 1318, 1318, 1318, 181, 1299, 1299, 1109, 1336,
- /* 290 */ 549, 1376, 1610, 1535, 1535, 1639, 1639, 1535, 1539, 1578,
- /* 300 */ 1670, 1546, 1652, 1546, 1681, 1681, 1681, 1681, 1535, 1694,
- /* 310 */ 1546, 1546, 1578, 1670, 1652, 1546, 1652, 1546, 1535, 1694,
- /* 320 */ 1565, 1665, 1535, 1694, 1708, 1535, 1694, 1535, 1694, 1708,
- /* 330 */ 1618, 1618, 1618, 1680, 1723, 1723, 1708, 1618, 1623, 1618,
- /* 340 */ 1680, 1618, 1618, 1588, 1708, 1640, 1640, 1708, 1611, 1643,
- /* 350 */ 1611, 1643, 1611, 1643, 1611, 1643, 1535, 1685, 1685, 1695,
- /* 360 */ 1695, 1636, 1641, 1761, 1535, 1631, 1636, 1644, 1646, 1546,
- /* 370 */ 1763, 1766, 1781, 1781, 1791, 1791, 1791, 1962, 1962, 1962,
- /* 380 */ 1962, 1962, 1962, 1962, 1962, 1962, 1962, 1962, 1962, 1962,
- /* 390 */ 1962, 1962, 308, 835, 954, 1232, 879, 715, 728, 1373,
- /* 400 */ 864, 1329, 1253, 1409, 297, 1431, 1489, 1497, 1520, 1521,
- /* 410 */ 1525, 1362, 1309, 1491, 1217, 1420, 1429, 1536, 1380, 1538,
- /* 420 */ 1293, 1354, 1548, 1585, 1434, 1342, 1813, 1816, 1798, 1664,
- /* 430 */ 1810, 1732, 1815, 1811, 1812, 1699, 1689, 1710, 1817, 1700,
- /* 440 */ 1819, 1701, 1826, 1843, 1705, 1697, 1719, 1787, 1814, 1702,
- /* 450 */ 1796, 1799, 1800, 1801, 1727, 1742, 1823, 1721, 1860, 1857,
- /* 460 */ 1841, 1751, 1707, 1802, 1840, 1803, 1792, 1827, 1730, 1759,
- /* 470 */ 1849, 1854, 1856, 1747, 1754, 1858, 1818, 1859, 1861, 1855,
- /* 480 */ 1862, 1820, 1829, 1865, 1783, 1863, 1864, 1825, 1845, 1867,
- /* 490 */ 1746, 1872, 1873, 1874, 1875, 1869, 1876, 1878, 1877, 1879,
- /* 500 */ 1881, 1880, 1767, 1882, 1884, 1794, 1883, 1887, 1769, 1885,
- /* 510 */ 1886, 1888, 1889, 1890, 1824, 1838, 1828, 1871, 1844, 1832,
- /* 520 */ 1892, 1893, 1896, 1897, 1901, 1902, 1895, 1907, 1885, 1908,
- /* 530 */ 1909, 1910, 1911, 1912, 1913, 1915, 1924, 1917, 1918, 1919,
- /* 540 */ 1920, 1922, 1923, 1921, 1808, 1807, 1809, 1821, 1822, 1926,
- /* 550 */ 1935, 1950, 1951,
+ /* 0 */ 1423, 1409, 1454, 1192, 1192, 382, 1252, 1410, 1517, 1684,
+ /* 10 */ 1684, 1684, 221, 0, 0, 180, 1015, 1684, 1684, 1684,
+ /* 20 */ 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684,
+ /* 30 */ 1049, 1049, 1121, 1121, 54, 616, 382, 382, 382, 382,
+ /* 40 */ 382, 40, 110, 219, 289, 396, 439, 509, 548, 618,
+ /* 50 */ 657, 727, 766, 836, 995, 1015, 1015, 1015, 1015, 1015,
+ /* 60 */ 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015, 1015,
+ /* 70 */ 1015, 1015, 1015, 1035, 1015, 1138, 880, 880, 1577, 1684,
+ /* 80 */ 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684,
+ /* 90 */ 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684,
+ /* 100 */ 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684, 1684,
+ /* 110 */ 1684, 1684, 1684, 1705, 1684, 1684, 1684, 1684, 1684, 1684,
+ /* 120 */ 1684, 1684, 1684, 1684, 1684, 1684, 1684, 146, 84, 84,
+ /* 130 */ 84, 84, 84, 274, 315, 125, 97, 357, 66, 66,
+ /* 140 */ 893, 258, 66, 66, 371, 371, 66, 551, 551, 551,
+ /* 150 */ 551, 192, 209, 209, 278, 127, 2023, 2023, 621, 621,
+ /* 160 */ 621, 201, 398, 398, 398, 398, 939, 939, 442, 936,
+ /* 170 */ 1009, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ /* 180 */ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ /* 190 */ 66, 710, 710, 66, 776, 435, 435, 410, 410, 372,
+ /* 200 */ 1097, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 250, 490,
+ /* 210 */ 490, 511, 451, 516, 252, 566, 575, 781, 673, 66,
+ /* 220 */ 66, 66, 66, 66, 66, 66, 66, 66, 66, 722,
+ /* 230 */ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ /* 240 */ 66, 66, 790, 790, 790, 66, 66, 66, 883, 66,
+ /* 250 */ 66, 66, 891, 1064, 66, 66, 1212, 66, 66, 66,
+ /* 260 */ 66, 66, 66, 66, 66, 725, 763, 177, 940, 940,
+ /* 270 */ 940, 940, 337, 177, 177, 1028, 1053, 670, 1264, 1179,
+ /* 280 */ 1173, 1254, 1316, 1173, 1316, 1336, 50, 1179, 1179, 50,
+ /* 290 */ 1179, 1254, 1336, 1259, 732, 532, 1347, 1347, 1347, 1316,
+ /* 300 */ 1236, 1236, 1184, 1356, 1167, 898, 1650, 1650, 1572, 1572,
+ /* 310 */ 1685, 1685, 1572, 1576, 1579, 1712, 1692, 1722, 1722, 1722,
+ /* 320 */ 1722, 1572, 1732, 1604, 1579, 1579, 1604, 1712, 1692, 1604,
+ /* 330 */ 1692, 1604, 1572, 1732, 1610, 1707, 1572, 1732, 1751, 1572,
+ /* 340 */ 1732, 1572, 1732, 1751, 1665, 1665, 1665, 1723, 1766, 1766,
+ /* 350 */ 1751, 1665, 1664, 1665, 1723, 1665, 1665, 1629, 1772, 1698,
+ /* 360 */ 1698, 1751, 1674, 1709, 1674, 1709, 1674, 1709, 1674, 1709,
+ /* 370 */ 1572, 1736, 1736, 1746, 1746, 1682, 1686, 1810, 1572, 1688,
+ /* 380 */ 1682, 1695, 1697, 1604, 1821, 1838, 1853, 1853, 1864, 1864,
+ /* 390 */ 1864, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,
+ /* 400 */ 2023, 2023, 2023, 2023, 2023, 2023, 232, 101, 1131, 1193,
+ /* 410 */ 619, 679, 841, 1421, 1286, 115, 1352, 1334, 1361, 1419,
+ /* 420 */ 1342, 1505, 1531, 1585, 1593, 1605, 1612, 1280, 1337, 1491,
+ /* 430 */ 1358, 1451, 1332, 1616, 1617, 1425, 1618, 1386, 1431, 1624,
+ /* 440 */ 1626, 1399, 1460, 1880, 1883, 1865, 1726, 1876, 1877, 1869,
+ /* 450 */ 1871, 1757, 1747, 1767, 1872, 1872, 1875, 1759, 1881, 1760,
+ /* 460 */ 1886, 1903, 1764, 1777, 1872, 1778, 1847, 1878, 1872, 1762,
+ /* 470 */ 1860, 1861, 1863, 1866, 1788, 1803, 1887, 1780, 1921, 1918,
+ /* 480 */ 1902, 1811, 1768, 1858, 1901, 1862, 1854, 1890, 1792, 1819,
+ /* 490 */ 1910, 1915, 1917, 1807, 1814, 1919, 1873, 1920, 1922, 1916,
+ /* 500 */ 1923, 1879, 1884, 1924, 1851, 1925, 1927, 1885, 1913, 1928,
+ /* 510 */ 1806, 1933, 1934, 1935, 1936, 1930, 1937, 1939, 1867, 1822,
+ /* 520 */ 1940, 1942, 1852, 1932, 1946, 1827, 1944, 1938, 1941, 1943,
+ /* 530 */ 1945, 1888, 1896, 1892, 1929, 1899, 1889, 1947, 1957, 1961,
+ /* 540 */ 1960, 1959, 1962, 1952, 1964, 1944, 1966, 1967, 1968, 1969,
+ /* 550 */ 1970, 1971, 1973, 1982, 1975, 1976, 1977, 1978, 1980, 1981,
+ /* 560 */ 1979, 1870, 1859, 1868, 1874, 1882, 1983, 1992, 2008, 2009,
};
-#define YY_REDUCE_COUNT (391)
-#define YY_REDUCE_MIN (-262)
-#define YY_REDUCE_MAX (1625)
+#define YY_REDUCE_COUNT (405)
+#define YY_REDUCE_MIN (-266)
+#define YY_REDUCE_MAX (1683)
static const short yy_reduce_ofst[] = {
- /* 0 */ 490, -122, 545, 645, 650, -120, -189, -187, -184, -182,
- /* 10 */ -178, -176, 45, 30, 200, -251, -134, 390, 392, 521,
- /* 20 */ 523, 213, 692, 821, 284, 589, 872, 666, 671, 866,
- /* 30 */ 71, 111, 273, 389, 686, 815, 904, 932, 948, 955,
- /* 40 */ 964, 969, -259, -259, -259, -259, -259, -259, -259, -259,
- /* 50 */ -259, -259, -259, -259, -259, -259, -259, -259, -259, -259,
- /* 60 */ -259, -259, -259, -259, -259, -259, -259, -259, -259, -259,
- /* 70 */ -259, -259, -259, -259, -259, -259, -259, -259, 428, 430,
- /* 80 */ 899, 985, 1021, 1028, 1057, 1069, 1081, 1108, 1110, 1115,
- /* 90 */ 1117, 1123, 1149, 1154, 1159, 1170, 1174, 1178, 1183, 1194,
- /* 100 */ 1198, 1204, 1208, 1212, 1218, 1222, 1229, 1278, 1280, 1283,
- /* 110 */ 1285, 1313, 1316, 1320, 1322, 1325, 1327, 1330, 1366, 1371,
- /* 120 */ 1379, 1387, 1417, 1425, 1430, 1432, -259, -259, -259, -259,
- /* 130 */ -259, -259, -259, -259, -259, 557, 974, -214, -174, -9,
- /* 140 */ 431, -124, 806, 925, 806, 925, 251, 928, 940, -259,
- /* 150 */ -259, -259, -259, -198, -198, -198, 127, -186, -168, 212,
- /* 160 */ 646, 617, 799, -262, 555, 220, 220, 491, 605, 1040,
- /* 170 */ 1060, 699, -11, 600, 848, 862, 345, -129, 724, -91,
- /* 180 */ 158, 749, 716, 900, 304, 822, 929, 926, 499, 793,
- /* 190 */ 322, 892, 813, 845, 958, 1056, 751, 905, 1133, 1062,
- /* 200 */ 803, -210, -185, -179, -148, -167, -89, 121, 274, 281,
- /* 210 */ 320, 336, 439, 663, 711, 957, 965, 1064, 1068, 1112,
- /* 220 */ 1116, -196, 1127, 1134, 1180, 1184, 1195, 1199, 1203, 1215,
- /* 230 */ 1223, 1250, 1267, 1286, 205, 422, 638, 1324, 1341, 1364,
- /* 240 */ 1365, 1213, 1392, 1399, 1403, 869, 1260, 1405, 1421, 1276,
- /* 250 */ 1424, 121, 1426, 1427, 1428, 1433, 1436, 1437, 1227, 1338,
- /* 260 */ 1284, 1359, 1370, 1377, 1388, 1213, 1284, 1284, 1385, 1438,
- /* 270 */ 1443, 1349, 1400, 1391, 1394, 1360, 1408, 1410, 1367, 1439,
- /* 280 */ 1440, 1435, 1442, 1446, 1447, 1397, 1413, 1418, 1390, 1444,
- /* 290 */ 1445, 1474, 1381, 1479, 1480, 1401, 1402, 1490, 1414, 1449,
- /* 300 */ 1452, 1453, 1467, 1456, 1469, 1470, 1477, 1478, 1515, 1518,
- /* 310 */ 1476, 1482, 1450, 1454, 1492, 1483, 1493, 1484, 1523, 1531,
- /* 320 */ 1457, 1455, 1532, 1534, 1516, 1537, 1540, 1543, 1541, 1526,
- /* 330 */ 1528, 1530, 1542, 1512, 1529, 1533, 1544, 1545, 1547, 1550,
- /* 340 */ 1549, 1551, 1554, 1458, 1552, 1494, 1495, 1556, 1498, 1502,
- /* 350 */ 1503, 1511, 1517, 1519, 1522, 1524, 1579, 1472, 1473, 1527,
- /* 360 */ 1555, 1557, 1559, 1558, 1589, 1560, 1561, 1564, 1566, 1568,
- /* 370 */ 1592, 1595, 1605, 1606, 1612, 1613, 1622, 1562, 1563, 1505,
- /* 380 */ 1609, 1604, 1608, 1614, 1615, 1616, 1596, 1597, 1617, 1620,
- /* 390 */ 1625, 1619,
+ /* 0 */ 111, 168, 272, 760, -177, -175, -192, -190, -182, -179,
+ /* 10 */ 216, 220, 481, -208, -205, -266, -140, -115, 241, 393,
+ /* 20 */ 523, 325, 612, 632, 542, 651, 764, 757, 702, 762,
+ /* 30 */ 812, 814, -188, 273, 924, 386, 758, 967, 1020, 1052,
+ /* 40 */ 1066, -256, -256, -256, -256, -256, -256, -256, -256, -256,
+ /* 50 */ -256, -256, -256, -256, -256, -256, -256, -256, -256, -256,
+ /* 60 */ -256, -256, -256, -256, -256, -256, -256, -256, -256, -256,
+ /* 70 */ -256, -256, -256, -256, -256, -256, -256, -256, 195, 222,
+ /* 80 */ 813, 917, 920, 959, 985, 1006, 1038, 1067, 1069, 1072,
+ /* 90 */ 1099, 1103, 1105, 1118, 1135, 1139, 1142, 1146, 1148, 1159,
+ /* 100 */ 1174, 1178, 1183, 1185, 1187, 1198, 1202, 1246, 1258, 1260,
+ /* 110 */ 1262, 1288, 1291, 1299, 1304, 1319, 1328, 1330, 1357, 1359,
+ /* 120 */ 1364, 1366, 1375, 1390, 1395, 1406, 1411, -256, -256, -256,
+ /* 130 */ -256, -256, -256, -256, -256, 447, -256, 555, -178, 605,
+ /* 140 */ 832, -220, 606, -94, -168, 36, -122, 730, 780, 730,
+ /* 150 */ 780, 918, -136, 338, -256, -256, -256, -256, 80, 80,
+ /* 160 */ 80, 720, 703, 811, 882, 903, -213, -204, 106, 330,
+ /* 170 */ 330, -77, 236, 320, 599, 67, 457, 675, 729, 395,
+ /* 180 */ 268, 611, 969, 1004, 726, 1014, 983, 123, 884, 608,
+ /* 190 */ 1034, 547, 911, 650, 844, 922, 949, 965, 972, 978,
+ /* 200 */ 449, 970, 718, 784, 1073, 1084, 1023, 1129, -209, -180,
+ /* 210 */ -113, 114, 183, 329, 345, 391, 446, 502, 609, 667,
+ /* 220 */ 713, 817, 865, 881, 901, 921, 989, 1191, 1195, 214,
+ /* 230 */ 1223, 1235, 1251, 1367, 1376, 1377, 1383, 1385, 1401, 1402,
+ /* 240 */ 1403, 1414, 584, 638, 1305, 1420, 1422, 1426, 1294, 1430,
+ /* 250 */ 1435, 1437, 1348, 1329, 1459, 1461, 1412, 1462, 345, 1463,
+ /* 260 */ 1464, 1465, 1466, 1467, 1468, 1378, 1380, 1427, 1408, 1413,
+ /* 270 */ 1415, 1428, 1294, 1427, 1427, 1433, 1450, 1473, 1381, 1417,
+ /* 280 */ 1424, 1432, 1434, 1436, 1438, 1387, 1443, 1429, 1439, 1444,
+ /* 290 */ 1440, 1453, 1388, 1481, 1455, 1457, 1483, 1485, 1488, 1456,
+ /* 300 */ 1469, 1470, 1441, 1471, 1474, 1512, 1416, 1442, 1519, 1522,
+ /* 310 */ 1446, 1447, 1523, 1472, 1475, 1476, 1504, 1507, 1511, 1513,
+ /* 320 */ 1514, 1548, 1553, 1510, 1486, 1487, 1515, 1490, 1528, 1518,
+ /* 330 */ 1529, 1521, 1564, 1568, 1480, 1484, 1571, 1574, 1555, 1578,
+ /* 340 */ 1580, 1581, 1583, 1556, 1566, 1567, 1569, 1561, 1570, 1573,
+ /* 350 */ 1575, 1582, 1584, 1587, 1588, 1590, 1591, 1498, 1506, 1534,
+ /* 360 */ 1535, 1597, 1560, 1586, 1565, 1589, 1592, 1594, 1595, 1598,
+ /* 370 */ 1627, 1516, 1520, 1599, 1600, 1601, 1596, 1558, 1635, 1602,
+ /* 380 */ 1607, 1619, 1603, 1606, 1651, 1666, 1675, 1676, 1680, 1681,
+ /* 390 */ 1683, 1608, 1609, 1613, 1668, 1667, 1669, 1670, 1671, 1672,
+ /* 400 */ 1655, 1656, 1673, 1677, 1679, 1678,
};
static const YYACTIONTYPE yy_default[] = {
- /* 0 */ 1575, 1575, 1575, 1411, 1188, 1297, 1188, 1188, 1188, 1411,
- /* 10 */ 1411, 1411, 1188, 1327, 1327, 1464, 1219, 1188, 1188, 1188,
- /* 20 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1410, 1188, 1188,
- /* 30 */ 1188, 1188, 1494, 1494, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 40 */ 1188, 1188, 1188, 1336, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 50 */ 1412, 1413, 1188, 1188, 1188, 1463, 1465, 1428, 1346, 1345,
- /* 60 */ 1344, 1343, 1446, 1314, 1341, 1334, 1338, 1406, 1407, 1405,
- /* 70 */ 1409, 1413, 1412, 1188, 1337, 1377, 1391, 1376, 1188, 1188,
- /* 80 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 90 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 100 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 110 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 120 */ 1188, 1188, 1188, 1188, 1188, 1188, 1385, 1390, 1396, 1389,
- /* 130 */ 1386, 1379, 1378, 1380, 1381, 1188, 1209, 1261, 1188, 1188,
- /* 140 */ 1188, 1188, 1482, 1481, 1188, 1188, 1219, 1371, 1370, 1382,
- /* 150 */ 1383, 1393, 1392, 1471, 1529, 1528, 1429, 1188, 1188, 1188,
- /* 160 */ 1188, 1188, 1188, 1494, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 170 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 180 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1494, 1494,
- /* 190 */ 1188, 1219, 1494, 1494, 1215, 1215, 1321, 1188, 1477, 1297,
- /* 200 */ 1288, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 210 */ 1188, 1188, 1188, 1188, 1188, 1468, 1466, 1188, 1188, 1188,
- /* 220 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 230 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 240 */ 1188, 1188, 1188, 1188, 1188, 1293, 1188, 1188, 1188, 1188,
- /* 250 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1523, 1188, 1441,
- /* 260 */ 1275, 1293, 1293, 1293, 1293, 1295, 1276, 1274, 1287, 1220,
- /* 270 */ 1195, 1567, 1294, 1316, 1316, 1564, 1340, 1294, 1564, 1236,
- /* 280 */ 1545, 1231, 1327, 1327, 1327, 1316, 1321, 1321, 1408, 1294,
- /* 290 */ 1287, 1188, 1567, 1302, 1302, 1566, 1566, 1302, 1429, 1349,
- /* 300 */ 1355, 1340, 1264, 1340, 1270, 1270, 1270, 1270, 1302, 1206,
- /* 310 */ 1340, 1340, 1349, 1355, 1264, 1340, 1264, 1340, 1302, 1206,
- /* 320 */ 1445, 1561, 1302, 1206, 1419, 1302, 1206, 1302, 1206, 1419,
- /* 330 */ 1262, 1262, 1262, 1251, 1188, 1188, 1419, 1262, 1236, 1262,
- /* 340 */ 1251, 1262, 1262, 1512, 1419, 1423, 1423, 1419, 1320, 1315,
- /* 350 */ 1320, 1315, 1320, 1315, 1320, 1315, 1302, 1504, 1504, 1330,
- /* 360 */ 1330, 1335, 1321, 1414, 1302, 1188, 1335, 1333, 1331, 1340,
- /* 370 */ 1212, 1254, 1526, 1526, 1522, 1522, 1522, 1572, 1572, 1477,
- /* 380 */ 1538, 1219, 1219, 1219, 1219, 1538, 1238, 1238, 1220, 1220,
- /* 390 */ 1219, 1538, 1188, 1188, 1188, 1188, 1188, 1188, 1533, 1188,
- /* 400 */ 1430, 1306, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 410 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 420 */ 1188, 1188, 1188, 1188, 1188, 1360, 1188, 1191, 1474, 1188,
- /* 430 */ 1188, 1472, 1188, 1188, 1188, 1188, 1188, 1188, 1307, 1188,
- /* 440 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 450 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1563, 1188, 1188,
- /* 460 */ 1188, 1188, 1188, 1188, 1444, 1443, 1188, 1188, 1304, 1188,
- /* 470 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 480 */ 1188, 1188, 1234, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 490 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 500 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1332,
- /* 510 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 520 */ 1188, 1188, 1188, 1188, 1509, 1322, 1188, 1188, 1554, 1188,
- /* 530 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188,
- /* 540 */ 1188, 1188, 1188, 1549, 1278, 1362, 1188, 1361, 1365, 1188,
- /* 550 */ 1200, 1188, 1188,
+ /* 0 */ 1623, 1623, 1623, 1453, 1223, 1332, 1223, 1223, 1223, 1453,
+ /* 10 */ 1453, 1453, 1223, 1362, 1362, 1506, 1254, 1223, 1223, 1223,
+ /* 20 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1452, 1223, 1223,
+ /* 30 */ 1223, 1223, 1541, 1541, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 40 */ 1223, 1223, 1371, 1223, 1378, 1223, 1223, 1223, 1223, 1223,
+ /* 50 */ 1454, 1455, 1223, 1223, 1223, 1505, 1507, 1470, 1385, 1384,
+ /* 60 */ 1383, 1382, 1488, 1349, 1376, 1369, 1373, 1448, 1449, 1447,
+ /* 70 */ 1451, 1455, 1454, 1223, 1372, 1419, 1433, 1418, 1223, 1223,
+ /* 80 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 90 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 100 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 110 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 120 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1427, 1432, 1438,
+ /* 130 */ 1431, 1428, 1421, 1420, 1422, 1223, 1423, 1223, 1223, 1223,
+ /* 140 */ 1244, 1296, 1223, 1223, 1223, 1223, 1223, 1525, 1524, 1223,
+ /* 150 */ 1223, 1254, 1413, 1412, 1424, 1425, 1435, 1434, 1513, 1576,
+ /* 160 */ 1575, 1471, 1223, 1223, 1223, 1223, 1223, 1223, 1541, 1223,
+ /* 170 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 180 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 190 */ 1223, 1541, 1541, 1223, 1254, 1541, 1541, 1250, 1250, 1356,
+ /* 200 */ 1223, 1520, 1323, 1323, 1323, 1323, 1332, 1323, 1223, 1223,
+ /* 210 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 220 */ 1223, 1223, 1223, 1510, 1508, 1223, 1223, 1223, 1223, 1223,
+ /* 230 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 240 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 250 */ 1223, 1223, 1328, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 260 */ 1223, 1223, 1223, 1223, 1570, 1223, 1483, 1310, 1328, 1328,
+ /* 270 */ 1328, 1328, 1330, 1311, 1309, 1322, 1255, 1230, 1615, 1388,
+ /* 280 */ 1377, 1329, 1351, 1377, 1351, 1612, 1375, 1388, 1388, 1375,
+ /* 290 */ 1388, 1329, 1612, 1271, 1592, 1266, 1362, 1362, 1362, 1351,
+ /* 300 */ 1356, 1356, 1450, 1329, 1322, 1223, 1615, 1615, 1337, 1337,
+ /* 310 */ 1614, 1614, 1337, 1471, 1599, 1397, 1299, 1305, 1305, 1305,
+ /* 320 */ 1305, 1337, 1241, 1375, 1599, 1599, 1375, 1397, 1299, 1375,
+ /* 330 */ 1299, 1375, 1337, 1241, 1487, 1609, 1337, 1241, 1461, 1337,
+ /* 340 */ 1241, 1337, 1241, 1461, 1297, 1297, 1297, 1286, 1223, 1223,
+ /* 350 */ 1461, 1297, 1271, 1297, 1286, 1297, 1297, 1559, 1223, 1465,
+ /* 360 */ 1465, 1461, 1355, 1350, 1355, 1350, 1355, 1350, 1355, 1350,
+ /* 370 */ 1337, 1551, 1551, 1365, 1365, 1370, 1356, 1456, 1337, 1223,
+ /* 380 */ 1370, 1368, 1366, 1375, 1247, 1289, 1573, 1573, 1569, 1569,
+ /* 390 */ 1569, 1620, 1620, 1520, 1585, 1254, 1254, 1254, 1254, 1585,
+ /* 400 */ 1273, 1273, 1255, 1255, 1254, 1585, 1223, 1223, 1223, 1223,
+ /* 410 */ 1223, 1223, 1580, 1223, 1515, 1472, 1341, 1223, 1223, 1223,
+ /* 420 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 430 */ 1223, 1526, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 440 */ 1223, 1223, 1402, 1223, 1226, 1517, 1223, 1223, 1223, 1223,
+ /* 450 */ 1223, 1223, 1223, 1223, 1379, 1380, 1342, 1223, 1223, 1223,
+ /* 460 */ 1223, 1223, 1223, 1223, 1394, 1223, 1223, 1223, 1389, 1223,
+ /* 470 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1611, 1223, 1223,
+ /* 480 */ 1223, 1223, 1223, 1223, 1486, 1485, 1223, 1223, 1339, 1223,
+ /* 490 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 500 */ 1223, 1223, 1269, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 510 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 520 */ 1223, 1223, 1223, 1223, 1223, 1223, 1367, 1223, 1223, 1223,
+ /* 530 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 540 */ 1223, 1556, 1357, 1223, 1223, 1602, 1223, 1223, 1223, 1223,
+ /* 550 */ 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223, 1223,
+ /* 560 */ 1596, 1313, 1404, 1223, 1403, 1407, 1223, 1235, 1223, 1223,
};
/********** End of lemon-generated parsing tables *****************************/
@@ -155291,6 +159295,7 @@ static const YYCODETYPE yyFallback[] = {
59, /* TIES => ID */
59, /* GENERATED => ID */
59, /* ALWAYS => ID */
+ 59, /* MATERIALIZED => ID */
59, /* REINDEX => ID */
59, /* RENAME => ID */
59, /* CTIME_KW => ID */
@@ -155342,6 +159347,7 @@ static const YYCODETYPE yyFallback[] = {
0, /* HAVING => nothing */
0, /* LIMIT => nothing */
0, /* WHERE => nothing */
+ 0, /* RETURNING => nothing */
0, /* INTO => nothing */
0, /* NOTHING => nothing */
0, /* FLOAT => nothing */
@@ -155373,6 +159379,7 @@ static const YYCODETYPE yyFallback[] = {
0, /* IF_NULL_ROW => nothing */
0, /* ASTERISK => nothing */
0, /* SPAN => nothing */
+ 0, /* ERROR => nothing */
0, /* SPACE => nothing */
0, /* ILLEGAL => nothing */
};
@@ -155428,6 +159435,7 @@ typedef struct yyParser yyParser;
#ifndef NDEBUG
/* #include <stdio.h> */
+/* #include <assert.h> */
static FILE *yyTraceFILE = 0;
static char *yyTracePrompt = 0;
#endif /* NDEBUG */
@@ -155559,219 +159567,226 @@ static const char *const yyTokenName[] = {
/* 94 */ "TIES",
/* 95 */ "GENERATED",
/* 96 */ "ALWAYS",
- /* 97 */ "REINDEX",
- /* 98 */ "RENAME",
- /* 99 */ "CTIME_KW",
- /* 100 */ "ANY",
- /* 101 */ "BITAND",
- /* 102 */ "BITOR",
- /* 103 */ "LSHIFT",
- /* 104 */ "RSHIFT",
- /* 105 */ "PLUS",
- /* 106 */ "MINUS",
- /* 107 */ "STAR",
- /* 108 */ "SLASH",
- /* 109 */ "REM",
- /* 110 */ "CONCAT",
- /* 111 */ "COLLATE",
- /* 112 */ "BITNOT",
- /* 113 */ "ON",
- /* 114 */ "INDEXED",
- /* 115 */ "STRING",
- /* 116 */ "JOIN_KW",
- /* 117 */ "CONSTRAINT",
- /* 118 */ "DEFAULT",
- /* 119 */ "NULL",
- /* 120 */ "PRIMARY",
- /* 121 */ "UNIQUE",
- /* 122 */ "CHECK",
- /* 123 */ "REFERENCES",
- /* 124 */ "AUTOINCR",
- /* 125 */ "INSERT",
- /* 126 */ "DELETE",
- /* 127 */ "UPDATE",
- /* 128 */ "SET",
- /* 129 */ "DEFERRABLE",
- /* 130 */ "FOREIGN",
- /* 131 */ "DROP",
- /* 132 */ "UNION",
- /* 133 */ "ALL",
- /* 134 */ "EXCEPT",
- /* 135 */ "INTERSECT",
- /* 136 */ "SELECT",
- /* 137 */ "VALUES",
- /* 138 */ "DISTINCT",
- /* 139 */ "DOT",
- /* 140 */ "FROM",
- /* 141 */ "JOIN",
- /* 142 */ "USING",
- /* 143 */ "ORDER",
- /* 144 */ "GROUP",
- /* 145 */ "HAVING",
- /* 146 */ "LIMIT",
- /* 147 */ "WHERE",
- /* 148 */ "INTO",
- /* 149 */ "NOTHING",
- /* 150 */ "FLOAT",
- /* 151 */ "BLOB",
- /* 152 */ "INTEGER",
- /* 153 */ "VARIABLE",
- /* 154 */ "CASE",
- /* 155 */ "WHEN",
- /* 156 */ "THEN",
- /* 157 */ "ELSE",
- /* 158 */ "INDEX",
- /* 159 */ "ALTER",
- /* 160 */ "ADD",
- /* 161 */ "WINDOW",
- /* 162 */ "OVER",
- /* 163 */ "FILTER",
- /* 164 */ "COLUMN",
- /* 165 */ "AGG_FUNCTION",
- /* 166 */ "AGG_COLUMN",
- /* 167 */ "TRUEFALSE",
- /* 168 */ "ISNOT",
- /* 169 */ "FUNCTION",
- /* 170 */ "UMINUS",
- /* 171 */ "UPLUS",
- /* 172 */ "TRUTH",
- /* 173 */ "REGISTER",
- /* 174 */ "VECTOR",
- /* 175 */ "SELECT_COLUMN",
- /* 176 */ "IF_NULL_ROW",
- /* 177 */ "ASTERISK",
- /* 178 */ "SPAN",
- /* 179 */ "SPACE",
- /* 180 */ "ILLEGAL",
- /* 181 */ "input",
- /* 182 */ "cmdlist",
- /* 183 */ "ecmd",
- /* 184 */ "cmdx",
- /* 185 */ "explain",
- /* 186 */ "cmd",
- /* 187 */ "transtype",
- /* 188 */ "trans_opt",
- /* 189 */ "nm",
- /* 190 */ "savepoint_opt",
- /* 191 */ "create_table",
- /* 192 */ "create_table_args",
- /* 193 */ "createkw",
- /* 194 */ "temp",
- /* 195 */ "ifnotexists",
- /* 196 */ "dbnm",
- /* 197 */ "columnlist",
- /* 198 */ "conslist_opt",
- /* 199 */ "table_options",
- /* 200 */ "select",
- /* 201 */ "columnname",
- /* 202 */ "carglist",
- /* 203 */ "typetoken",
- /* 204 */ "typename",
- /* 205 */ "signed",
- /* 206 */ "plus_num",
- /* 207 */ "minus_num",
- /* 208 */ "scanpt",
- /* 209 */ "scantok",
- /* 210 */ "ccons",
- /* 211 */ "term",
- /* 212 */ "expr",
- /* 213 */ "onconf",
- /* 214 */ "sortorder",
- /* 215 */ "autoinc",
- /* 216 */ "eidlist_opt",
- /* 217 */ "refargs",
- /* 218 */ "defer_subclause",
- /* 219 */ "generated",
- /* 220 */ "refarg",
- /* 221 */ "refact",
- /* 222 */ "init_deferred_pred_opt",
- /* 223 */ "conslist",
- /* 224 */ "tconscomma",
- /* 225 */ "tcons",
- /* 226 */ "sortlist",
- /* 227 */ "eidlist",
- /* 228 */ "defer_subclause_opt",
- /* 229 */ "orconf",
- /* 230 */ "resolvetype",
- /* 231 */ "raisetype",
- /* 232 */ "ifexists",
- /* 233 */ "fullname",
- /* 234 */ "selectnowith",
- /* 235 */ "oneselect",
- /* 236 */ "wqlist",
- /* 237 */ "multiselect_op",
- /* 238 */ "distinct",
- /* 239 */ "selcollist",
- /* 240 */ "from",
- /* 241 */ "where_opt",
- /* 242 */ "groupby_opt",
- /* 243 */ "having_opt",
- /* 244 */ "orderby_opt",
- /* 245 */ "limit_opt",
- /* 246 */ "window_clause",
- /* 247 */ "values",
- /* 248 */ "nexprlist",
- /* 249 */ "sclp",
- /* 250 */ "as",
- /* 251 */ "seltablist",
- /* 252 */ "stl_prefix",
- /* 253 */ "joinop",
- /* 254 */ "indexed_opt",
- /* 255 */ "on_opt",
- /* 256 */ "using_opt",
- /* 257 */ "exprlist",
- /* 258 */ "xfullname",
- /* 259 */ "idlist",
- /* 260 */ "nulls",
- /* 261 */ "with",
- /* 262 */ "setlist",
- /* 263 */ "insert_cmd",
- /* 264 */ "idlist_opt",
- /* 265 */ "upsert",
- /* 266 */ "filter_over",
- /* 267 */ "likeop",
- /* 268 */ "between_op",
- /* 269 */ "in_op",
- /* 270 */ "paren_exprlist",
- /* 271 */ "case_operand",
- /* 272 */ "case_exprlist",
- /* 273 */ "case_else",
- /* 274 */ "uniqueflag",
- /* 275 */ "collate",
- /* 276 */ "vinto",
- /* 277 */ "nmnum",
- /* 278 */ "trigger_decl",
- /* 279 */ "trigger_cmd_list",
- /* 280 */ "trigger_time",
- /* 281 */ "trigger_event",
- /* 282 */ "foreach_clause",
- /* 283 */ "when_clause",
- /* 284 */ "trigger_cmd",
- /* 285 */ "trnm",
- /* 286 */ "tridxby",
- /* 287 */ "database_kw_opt",
- /* 288 */ "key_opt",
- /* 289 */ "add_column_fullname",
- /* 290 */ "kwcolumn_opt",
- /* 291 */ "create_vtab",
- /* 292 */ "vtabarglist",
- /* 293 */ "vtabarg",
- /* 294 */ "vtabargtoken",
- /* 295 */ "lp",
- /* 296 */ "anylist",
- /* 297 */ "windowdefn_list",
- /* 298 */ "windowdefn",
- /* 299 */ "window",
- /* 300 */ "frame_opt",
- /* 301 */ "part_opt",
- /* 302 */ "filter_clause",
- /* 303 */ "over_clause",
- /* 304 */ "range_or_rows",
- /* 305 */ "frame_bound",
- /* 306 */ "frame_bound_s",
- /* 307 */ "frame_bound_e",
- /* 308 */ "frame_exclude_opt",
- /* 309 */ "frame_exclude",
+ /* 97 */ "MATERIALIZED",
+ /* 98 */ "REINDEX",
+ /* 99 */ "RENAME",
+ /* 100 */ "CTIME_KW",
+ /* 101 */ "ANY",
+ /* 102 */ "BITAND",
+ /* 103 */ "BITOR",
+ /* 104 */ "LSHIFT",
+ /* 105 */ "RSHIFT",
+ /* 106 */ "PLUS",
+ /* 107 */ "MINUS",
+ /* 108 */ "STAR",
+ /* 109 */ "SLASH",
+ /* 110 */ "REM",
+ /* 111 */ "CONCAT",
+ /* 112 */ "COLLATE",
+ /* 113 */ "BITNOT",
+ /* 114 */ "ON",
+ /* 115 */ "INDEXED",
+ /* 116 */ "STRING",
+ /* 117 */ "JOIN_KW",
+ /* 118 */ "CONSTRAINT",
+ /* 119 */ "DEFAULT",
+ /* 120 */ "NULL",
+ /* 121 */ "PRIMARY",
+ /* 122 */ "UNIQUE",
+ /* 123 */ "CHECK",
+ /* 124 */ "REFERENCES",
+ /* 125 */ "AUTOINCR",
+ /* 126 */ "INSERT",
+ /* 127 */ "DELETE",
+ /* 128 */ "UPDATE",
+ /* 129 */ "SET",
+ /* 130 */ "DEFERRABLE",
+ /* 131 */ "FOREIGN",
+ /* 132 */ "DROP",
+ /* 133 */ "UNION",
+ /* 134 */ "ALL",
+ /* 135 */ "EXCEPT",
+ /* 136 */ "INTERSECT",
+ /* 137 */ "SELECT",
+ /* 138 */ "VALUES",
+ /* 139 */ "DISTINCT",
+ /* 140 */ "DOT",
+ /* 141 */ "FROM",
+ /* 142 */ "JOIN",
+ /* 143 */ "USING",
+ /* 144 */ "ORDER",
+ /* 145 */ "GROUP",
+ /* 146 */ "HAVING",
+ /* 147 */ "LIMIT",
+ /* 148 */ "WHERE",
+ /* 149 */ "RETURNING",
+ /* 150 */ "INTO",
+ /* 151 */ "NOTHING",
+ /* 152 */ "FLOAT",
+ /* 153 */ "BLOB",
+ /* 154 */ "INTEGER",
+ /* 155 */ "VARIABLE",
+ /* 156 */ "CASE",
+ /* 157 */ "WHEN",
+ /* 158 */ "THEN",
+ /* 159 */ "ELSE",
+ /* 160 */ "INDEX",
+ /* 161 */ "ALTER",
+ /* 162 */ "ADD",
+ /* 163 */ "WINDOW",
+ /* 164 */ "OVER",
+ /* 165 */ "FILTER",
+ /* 166 */ "COLUMN",
+ /* 167 */ "AGG_FUNCTION",
+ /* 168 */ "AGG_COLUMN",
+ /* 169 */ "TRUEFALSE",
+ /* 170 */ "ISNOT",
+ /* 171 */ "FUNCTION",
+ /* 172 */ "UMINUS",
+ /* 173 */ "UPLUS",
+ /* 174 */ "TRUTH",
+ /* 175 */ "REGISTER",
+ /* 176 */ "VECTOR",
+ /* 177 */ "SELECT_COLUMN",
+ /* 178 */ "IF_NULL_ROW",
+ /* 179 */ "ASTERISK",
+ /* 180 */ "SPAN",
+ /* 181 */ "ERROR",
+ /* 182 */ "SPACE",
+ /* 183 */ "ILLEGAL",
+ /* 184 */ "input",
+ /* 185 */ "cmdlist",
+ /* 186 */ "ecmd",
+ /* 187 */ "cmdx",
+ /* 188 */ "explain",
+ /* 189 */ "cmd",
+ /* 190 */ "transtype",
+ /* 191 */ "trans_opt",
+ /* 192 */ "nm",
+ /* 193 */ "savepoint_opt",
+ /* 194 */ "create_table",
+ /* 195 */ "create_table_args",
+ /* 196 */ "createkw",
+ /* 197 */ "temp",
+ /* 198 */ "ifnotexists",
+ /* 199 */ "dbnm",
+ /* 200 */ "columnlist",
+ /* 201 */ "conslist_opt",
+ /* 202 */ "table_options",
+ /* 203 */ "select",
+ /* 204 */ "columnname",
+ /* 205 */ "carglist",
+ /* 206 */ "typetoken",
+ /* 207 */ "typename",
+ /* 208 */ "signed",
+ /* 209 */ "plus_num",
+ /* 210 */ "minus_num",
+ /* 211 */ "scanpt",
+ /* 212 */ "scantok",
+ /* 213 */ "ccons",
+ /* 214 */ "term",
+ /* 215 */ "expr",
+ /* 216 */ "onconf",
+ /* 217 */ "sortorder",
+ /* 218 */ "autoinc",
+ /* 219 */ "eidlist_opt",
+ /* 220 */ "refargs",
+ /* 221 */ "defer_subclause",
+ /* 222 */ "generated",
+ /* 223 */ "refarg",
+ /* 224 */ "refact",
+ /* 225 */ "init_deferred_pred_opt",
+ /* 226 */ "conslist",
+ /* 227 */ "tconscomma",
+ /* 228 */ "tcons",
+ /* 229 */ "sortlist",
+ /* 230 */ "eidlist",
+ /* 231 */ "defer_subclause_opt",
+ /* 232 */ "orconf",
+ /* 233 */ "resolvetype",
+ /* 234 */ "raisetype",
+ /* 235 */ "ifexists",
+ /* 236 */ "fullname",
+ /* 237 */ "selectnowith",
+ /* 238 */ "oneselect",
+ /* 239 */ "wqlist",
+ /* 240 */ "multiselect_op",
+ /* 241 */ "distinct",
+ /* 242 */ "selcollist",
+ /* 243 */ "from",
+ /* 244 */ "where_opt",
+ /* 245 */ "groupby_opt",
+ /* 246 */ "having_opt",
+ /* 247 */ "orderby_opt",
+ /* 248 */ "limit_opt",
+ /* 249 */ "window_clause",
+ /* 250 */ "values",
+ /* 251 */ "nexprlist",
+ /* 252 */ "sclp",
+ /* 253 */ "as",
+ /* 254 */ "seltablist",
+ /* 255 */ "stl_prefix",
+ /* 256 */ "joinop",
+ /* 257 */ "indexed_opt",
+ /* 258 */ "on_opt",
+ /* 259 */ "using_opt",
+ /* 260 */ "exprlist",
+ /* 261 */ "xfullname",
+ /* 262 */ "idlist",
+ /* 263 */ "nulls",
+ /* 264 */ "with",
+ /* 265 */ "where_opt_ret",
+ /* 266 */ "setlist",
+ /* 267 */ "insert_cmd",
+ /* 268 */ "idlist_opt",
+ /* 269 */ "upsert",
+ /* 270 */ "returning",
+ /* 271 */ "filter_over",
+ /* 272 */ "likeop",
+ /* 273 */ "between_op",
+ /* 274 */ "in_op",
+ /* 275 */ "paren_exprlist",
+ /* 276 */ "case_operand",
+ /* 277 */ "case_exprlist",
+ /* 278 */ "case_else",
+ /* 279 */ "uniqueflag",
+ /* 280 */ "collate",
+ /* 281 */ "vinto",
+ /* 282 */ "nmnum",
+ /* 283 */ "trigger_decl",
+ /* 284 */ "trigger_cmd_list",
+ /* 285 */ "trigger_time",
+ /* 286 */ "trigger_event",
+ /* 287 */ "foreach_clause",
+ /* 288 */ "when_clause",
+ /* 289 */ "trigger_cmd",
+ /* 290 */ "trnm",
+ /* 291 */ "tridxby",
+ /* 292 */ "database_kw_opt",
+ /* 293 */ "key_opt",
+ /* 294 */ "add_column_fullname",
+ /* 295 */ "kwcolumn_opt",
+ /* 296 */ "create_vtab",
+ /* 297 */ "vtabarglist",
+ /* 298 */ "vtabarg",
+ /* 299 */ "vtabargtoken",
+ /* 300 */ "lp",
+ /* 301 */ "anylist",
+ /* 302 */ "wqitem",
+ /* 303 */ "wqas",
+ /* 304 */ "windowdefn_list",
+ /* 305 */ "windowdefn",
+ /* 306 */ "window",
+ /* 307 */ "frame_opt",
+ /* 308 */ "part_opt",
+ /* 309 */ "filter_clause",
+ /* 310 */ "over_clause",
+ /* 311 */ "range_or_rows",
+ /* 312 */ "frame_bound",
+ /* 313 */ "frame_bound_s",
+ /* 314 */ "frame_bound_e",
+ /* 315 */ "frame_exclude_opt",
+ /* 316 */ "frame_exclude",
};
#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */
@@ -155927,243 +159942,256 @@ static const char *const yyRuleName[] = {
/* 145 */ "limit_opt ::= LIMIT expr",
/* 146 */ "limit_opt ::= LIMIT expr OFFSET expr",
/* 147 */ "limit_opt ::= LIMIT expr COMMA expr",
- /* 148 */ "cmd ::= with DELETE FROM xfullname indexed_opt where_opt",
+ /* 148 */ "cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret",
/* 149 */ "where_opt ::=",
/* 150 */ "where_opt ::= WHERE expr",
- /* 151 */ "cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt",
- /* 152 */ "setlist ::= setlist COMMA nm EQ expr",
- /* 153 */ "setlist ::= setlist COMMA LP idlist RP EQ expr",
- /* 154 */ "setlist ::= nm EQ expr",
- /* 155 */ "setlist ::= LP idlist RP EQ expr",
- /* 156 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert",
- /* 157 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES",
- /* 158 */ "upsert ::=",
- /* 159 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt",
- /* 160 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING",
- /* 161 */ "upsert ::= ON CONFLICT DO NOTHING",
- /* 162 */ "insert_cmd ::= INSERT orconf",
- /* 163 */ "insert_cmd ::= REPLACE",
- /* 164 */ "idlist_opt ::=",
- /* 165 */ "idlist_opt ::= LP idlist RP",
- /* 166 */ "idlist ::= idlist COMMA nm",
- /* 167 */ "idlist ::= nm",
- /* 168 */ "expr ::= LP expr RP",
- /* 169 */ "expr ::= ID|INDEXED",
- /* 170 */ "expr ::= JOIN_KW",
- /* 171 */ "expr ::= nm DOT nm",
- /* 172 */ "expr ::= nm DOT nm DOT nm",
- /* 173 */ "term ::= NULL|FLOAT|BLOB",
- /* 174 */ "term ::= STRING",
- /* 175 */ "term ::= INTEGER",
- /* 176 */ "expr ::= VARIABLE",
- /* 177 */ "expr ::= expr COLLATE ID|STRING",
- /* 178 */ "expr ::= CAST LP expr AS typetoken RP",
- /* 179 */ "expr ::= ID|INDEXED LP distinct exprlist RP",
- /* 180 */ "expr ::= ID|INDEXED LP STAR RP",
- /* 181 */ "expr ::= ID|INDEXED LP distinct exprlist RP filter_over",
- /* 182 */ "expr ::= ID|INDEXED LP STAR RP filter_over",
- /* 183 */ "term ::= CTIME_KW",
- /* 184 */ "expr ::= LP nexprlist COMMA expr RP",
- /* 185 */ "expr ::= expr AND expr",
- /* 186 */ "expr ::= expr OR expr",
- /* 187 */ "expr ::= expr LT|GT|GE|LE expr",
- /* 188 */ "expr ::= expr EQ|NE expr",
- /* 189 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr",
- /* 190 */ "expr ::= expr PLUS|MINUS expr",
- /* 191 */ "expr ::= expr STAR|SLASH|REM expr",
- /* 192 */ "expr ::= expr CONCAT expr",
- /* 193 */ "likeop ::= NOT LIKE_KW|MATCH",
- /* 194 */ "expr ::= expr likeop expr",
- /* 195 */ "expr ::= expr likeop expr ESCAPE expr",
- /* 196 */ "expr ::= expr ISNULL|NOTNULL",
- /* 197 */ "expr ::= expr NOT NULL",
- /* 198 */ "expr ::= expr IS expr",
- /* 199 */ "expr ::= expr IS NOT expr",
- /* 200 */ "expr ::= NOT expr",
- /* 201 */ "expr ::= BITNOT expr",
- /* 202 */ "expr ::= PLUS|MINUS expr",
- /* 203 */ "between_op ::= BETWEEN",
- /* 204 */ "between_op ::= NOT BETWEEN",
- /* 205 */ "expr ::= expr between_op expr AND expr",
- /* 206 */ "in_op ::= IN",
- /* 207 */ "in_op ::= NOT IN",
- /* 208 */ "expr ::= expr in_op LP exprlist RP",
- /* 209 */ "expr ::= LP select RP",
- /* 210 */ "expr ::= expr in_op LP select RP",
- /* 211 */ "expr ::= expr in_op nm dbnm paren_exprlist",
- /* 212 */ "expr ::= EXISTS LP select RP",
- /* 213 */ "expr ::= CASE case_operand case_exprlist case_else END",
- /* 214 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr",
- /* 215 */ "case_exprlist ::= WHEN expr THEN expr",
- /* 216 */ "case_else ::= ELSE expr",
- /* 217 */ "case_else ::=",
- /* 218 */ "case_operand ::= expr",
- /* 219 */ "case_operand ::=",
- /* 220 */ "exprlist ::=",
- /* 221 */ "nexprlist ::= nexprlist COMMA expr",
- /* 222 */ "nexprlist ::= expr",
- /* 223 */ "paren_exprlist ::=",
- /* 224 */ "paren_exprlist ::= LP exprlist RP",
- /* 225 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt",
- /* 226 */ "uniqueflag ::= UNIQUE",
- /* 227 */ "uniqueflag ::=",
- /* 228 */ "eidlist_opt ::=",
- /* 229 */ "eidlist_opt ::= LP eidlist RP",
- /* 230 */ "eidlist ::= eidlist COMMA nm collate sortorder",
- /* 231 */ "eidlist ::= nm collate sortorder",
- /* 232 */ "collate ::=",
- /* 233 */ "collate ::= COLLATE ID|STRING",
- /* 234 */ "cmd ::= DROP INDEX ifexists fullname",
- /* 235 */ "cmd ::= VACUUM vinto",
- /* 236 */ "cmd ::= VACUUM nm vinto",
- /* 237 */ "vinto ::= INTO expr",
- /* 238 */ "vinto ::=",
- /* 239 */ "cmd ::= PRAGMA nm dbnm",
- /* 240 */ "cmd ::= PRAGMA nm dbnm EQ nmnum",
- /* 241 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP",
- /* 242 */ "cmd ::= PRAGMA nm dbnm EQ minus_num",
- /* 243 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP",
- /* 244 */ "plus_num ::= PLUS INTEGER|FLOAT",
- /* 245 */ "minus_num ::= MINUS INTEGER|FLOAT",
- /* 246 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END",
- /* 247 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause",
- /* 248 */ "trigger_time ::= BEFORE|AFTER",
- /* 249 */ "trigger_time ::= INSTEAD OF",
- /* 250 */ "trigger_time ::=",
- /* 251 */ "trigger_event ::= DELETE|INSERT",
- /* 252 */ "trigger_event ::= UPDATE",
- /* 253 */ "trigger_event ::= UPDATE OF idlist",
- /* 254 */ "when_clause ::=",
- /* 255 */ "when_clause ::= WHEN expr",
- /* 256 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI",
- /* 257 */ "trigger_cmd_list ::= trigger_cmd SEMI",
- /* 258 */ "trnm ::= nm DOT nm",
- /* 259 */ "tridxby ::= INDEXED BY nm",
- /* 260 */ "tridxby ::= NOT INDEXED",
- /* 261 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt",
- /* 262 */ "trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt",
- /* 263 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt",
- /* 264 */ "trigger_cmd ::= scanpt select scanpt",
- /* 265 */ "expr ::= RAISE LP IGNORE RP",
- /* 266 */ "expr ::= RAISE LP raisetype COMMA nm RP",
- /* 267 */ "raisetype ::= ROLLBACK",
- /* 268 */ "raisetype ::= ABORT",
- /* 269 */ "raisetype ::= FAIL",
- /* 270 */ "cmd ::= DROP TRIGGER ifexists fullname",
- /* 271 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt",
- /* 272 */ "cmd ::= DETACH database_kw_opt expr",
- /* 273 */ "key_opt ::=",
- /* 274 */ "key_opt ::= KEY expr",
- /* 275 */ "cmd ::= REINDEX",
- /* 276 */ "cmd ::= REINDEX nm dbnm",
- /* 277 */ "cmd ::= ANALYZE",
- /* 278 */ "cmd ::= ANALYZE nm dbnm",
- /* 279 */ "cmd ::= ALTER TABLE fullname RENAME TO nm",
- /* 280 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist",
- /* 281 */ "add_column_fullname ::= fullname",
- /* 282 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm",
- /* 283 */ "cmd ::= create_vtab",
- /* 284 */ "cmd ::= create_vtab LP vtabarglist RP",
- /* 285 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm",
- /* 286 */ "vtabarg ::=",
- /* 287 */ "vtabargtoken ::= ANY",
- /* 288 */ "vtabargtoken ::= lp anylist RP",
- /* 289 */ "lp ::= LP",
- /* 290 */ "with ::= WITH wqlist",
- /* 291 */ "with ::= WITH RECURSIVE wqlist",
- /* 292 */ "wqlist ::= nm eidlist_opt AS LP select RP",
- /* 293 */ "wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP",
- /* 294 */ "windowdefn_list ::= windowdefn",
- /* 295 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn",
- /* 296 */ "windowdefn ::= nm AS LP window RP",
- /* 297 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt",
- /* 298 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt",
- /* 299 */ "window ::= ORDER BY sortlist frame_opt",
- /* 300 */ "window ::= nm ORDER BY sortlist frame_opt",
- /* 301 */ "window ::= frame_opt",
- /* 302 */ "window ::= nm frame_opt",
- /* 303 */ "frame_opt ::=",
- /* 304 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt",
- /* 305 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt",
- /* 306 */ "range_or_rows ::= RANGE|ROWS|GROUPS",
- /* 307 */ "frame_bound_s ::= frame_bound",
- /* 308 */ "frame_bound_s ::= UNBOUNDED PRECEDING",
- /* 309 */ "frame_bound_e ::= frame_bound",
- /* 310 */ "frame_bound_e ::= UNBOUNDED FOLLOWING",
- /* 311 */ "frame_bound ::= expr PRECEDING|FOLLOWING",
- /* 312 */ "frame_bound ::= CURRENT ROW",
- /* 313 */ "frame_exclude_opt ::=",
- /* 314 */ "frame_exclude_opt ::= EXCLUDE frame_exclude",
- /* 315 */ "frame_exclude ::= NO OTHERS",
- /* 316 */ "frame_exclude ::= CURRENT ROW",
- /* 317 */ "frame_exclude ::= GROUP|TIES",
- /* 318 */ "window_clause ::= WINDOW windowdefn_list",
- /* 319 */ "filter_over ::= filter_clause over_clause",
- /* 320 */ "filter_over ::= over_clause",
- /* 321 */ "filter_over ::= filter_clause",
- /* 322 */ "over_clause ::= OVER LP window RP",
- /* 323 */ "over_clause ::= OVER nm",
- /* 324 */ "filter_clause ::= FILTER LP WHERE expr RP",
- /* 325 */ "input ::= cmdlist",
- /* 326 */ "cmdlist ::= cmdlist ecmd",
- /* 327 */ "cmdlist ::= ecmd",
- /* 328 */ "ecmd ::= SEMI",
- /* 329 */ "ecmd ::= cmdx SEMI",
- /* 330 */ "ecmd ::= explain cmdx SEMI",
- /* 331 */ "trans_opt ::=",
- /* 332 */ "trans_opt ::= TRANSACTION",
- /* 333 */ "trans_opt ::= TRANSACTION nm",
- /* 334 */ "savepoint_opt ::= SAVEPOINT",
- /* 335 */ "savepoint_opt ::=",
- /* 336 */ "cmd ::= create_table create_table_args",
- /* 337 */ "columnlist ::= columnlist COMMA columnname carglist",
- /* 338 */ "columnlist ::= columnname carglist",
- /* 339 */ "nm ::= ID|INDEXED",
- /* 340 */ "nm ::= STRING",
- /* 341 */ "nm ::= JOIN_KW",
- /* 342 */ "typetoken ::= typename",
- /* 343 */ "typename ::= ID|STRING",
- /* 344 */ "signed ::= plus_num",
- /* 345 */ "signed ::= minus_num",
- /* 346 */ "carglist ::= carglist ccons",
- /* 347 */ "carglist ::=",
- /* 348 */ "ccons ::= NULL onconf",
- /* 349 */ "ccons ::= GENERATED ALWAYS AS generated",
- /* 350 */ "ccons ::= AS generated",
- /* 351 */ "conslist_opt ::= COMMA conslist",
- /* 352 */ "conslist ::= conslist tconscomma tcons",
- /* 353 */ "conslist ::= tcons",
- /* 354 */ "tconscomma ::=",
- /* 355 */ "defer_subclause_opt ::= defer_subclause",
- /* 356 */ "resolvetype ::= raisetype",
- /* 357 */ "selectnowith ::= oneselect",
- /* 358 */ "oneselect ::= values",
- /* 359 */ "sclp ::= selcollist COMMA",
- /* 360 */ "as ::= ID|STRING",
- /* 361 */ "expr ::= term",
- /* 362 */ "likeop ::= LIKE_KW|MATCH",
- /* 363 */ "exprlist ::= nexprlist",
- /* 364 */ "nmnum ::= plus_num",
- /* 365 */ "nmnum ::= nm",
- /* 366 */ "nmnum ::= ON",
- /* 367 */ "nmnum ::= DELETE",
- /* 368 */ "nmnum ::= DEFAULT",
- /* 369 */ "plus_num ::= INTEGER|FLOAT",
- /* 370 */ "foreach_clause ::=",
- /* 371 */ "foreach_clause ::= FOR EACH ROW",
- /* 372 */ "trnm ::= nm",
- /* 373 */ "tridxby ::=",
- /* 374 */ "database_kw_opt ::= DATABASE",
- /* 375 */ "database_kw_opt ::=",
- /* 376 */ "kwcolumn_opt ::=",
- /* 377 */ "kwcolumn_opt ::= COLUMNKW",
- /* 378 */ "vtabarglist ::= vtabarg",
- /* 379 */ "vtabarglist ::= vtabarglist COMMA vtabarg",
- /* 380 */ "vtabarg ::= vtabarg vtabargtoken",
- /* 381 */ "anylist ::=",
- /* 382 */ "anylist ::= anylist LP anylist RP",
- /* 383 */ "anylist ::= anylist ANY",
- /* 384 */ "with ::=",
+ /* 151 */ "where_opt_ret ::=",
+ /* 152 */ "where_opt_ret ::= WHERE expr",
+ /* 153 */ "where_opt_ret ::= RETURNING selcollist",
+ /* 154 */ "where_opt_ret ::= WHERE expr RETURNING selcollist",
+ /* 155 */ "cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret",
+ /* 156 */ "setlist ::= setlist COMMA nm EQ expr",
+ /* 157 */ "setlist ::= setlist COMMA LP idlist RP EQ expr",
+ /* 158 */ "setlist ::= nm EQ expr",
+ /* 159 */ "setlist ::= LP idlist RP EQ expr",
+ /* 160 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert",
+ /* 161 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning",
+ /* 162 */ "upsert ::=",
+ /* 163 */ "upsert ::= RETURNING selcollist",
+ /* 164 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert",
+ /* 165 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert",
+ /* 166 */ "upsert ::= ON CONFLICT DO NOTHING returning",
+ /* 167 */ "upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning",
+ /* 168 */ "returning ::= RETURNING selcollist",
+ /* 169 */ "insert_cmd ::= INSERT orconf",
+ /* 170 */ "insert_cmd ::= REPLACE",
+ /* 171 */ "idlist_opt ::=",
+ /* 172 */ "idlist_opt ::= LP idlist RP",
+ /* 173 */ "idlist ::= idlist COMMA nm",
+ /* 174 */ "idlist ::= nm",
+ /* 175 */ "expr ::= LP expr RP",
+ /* 176 */ "expr ::= ID|INDEXED",
+ /* 177 */ "expr ::= JOIN_KW",
+ /* 178 */ "expr ::= nm DOT nm",
+ /* 179 */ "expr ::= nm DOT nm DOT nm",
+ /* 180 */ "term ::= NULL|FLOAT|BLOB",
+ /* 181 */ "term ::= STRING",
+ /* 182 */ "term ::= INTEGER",
+ /* 183 */ "expr ::= VARIABLE",
+ /* 184 */ "expr ::= expr COLLATE ID|STRING",
+ /* 185 */ "expr ::= CAST LP expr AS typetoken RP",
+ /* 186 */ "expr ::= ID|INDEXED LP distinct exprlist RP",
+ /* 187 */ "expr ::= ID|INDEXED LP STAR RP",
+ /* 188 */ "expr ::= ID|INDEXED LP distinct exprlist RP filter_over",
+ /* 189 */ "expr ::= ID|INDEXED LP STAR RP filter_over",
+ /* 190 */ "term ::= CTIME_KW",
+ /* 191 */ "expr ::= LP nexprlist COMMA expr RP",
+ /* 192 */ "expr ::= expr AND expr",
+ /* 193 */ "expr ::= expr OR expr",
+ /* 194 */ "expr ::= expr LT|GT|GE|LE expr",
+ /* 195 */ "expr ::= expr EQ|NE expr",
+ /* 196 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr",
+ /* 197 */ "expr ::= expr PLUS|MINUS expr",
+ /* 198 */ "expr ::= expr STAR|SLASH|REM expr",
+ /* 199 */ "expr ::= expr CONCAT expr",
+ /* 200 */ "likeop ::= NOT LIKE_KW|MATCH",
+ /* 201 */ "expr ::= expr likeop expr",
+ /* 202 */ "expr ::= expr likeop expr ESCAPE expr",
+ /* 203 */ "expr ::= expr ISNULL|NOTNULL",
+ /* 204 */ "expr ::= expr NOT NULL",
+ /* 205 */ "expr ::= expr IS expr",
+ /* 206 */ "expr ::= expr IS NOT expr",
+ /* 207 */ "expr ::= NOT expr",
+ /* 208 */ "expr ::= BITNOT expr",
+ /* 209 */ "expr ::= PLUS|MINUS expr",
+ /* 210 */ "between_op ::= BETWEEN",
+ /* 211 */ "between_op ::= NOT BETWEEN",
+ /* 212 */ "expr ::= expr between_op expr AND expr",
+ /* 213 */ "in_op ::= IN",
+ /* 214 */ "in_op ::= NOT IN",
+ /* 215 */ "expr ::= expr in_op LP exprlist RP",
+ /* 216 */ "expr ::= LP select RP",
+ /* 217 */ "expr ::= expr in_op LP select RP",
+ /* 218 */ "expr ::= expr in_op nm dbnm paren_exprlist",
+ /* 219 */ "expr ::= EXISTS LP select RP",
+ /* 220 */ "expr ::= CASE case_operand case_exprlist case_else END",
+ /* 221 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr",
+ /* 222 */ "case_exprlist ::= WHEN expr THEN expr",
+ /* 223 */ "case_else ::= ELSE expr",
+ /* 224 */ "case_else ::=",
+ /* 225 */ "case_operand ::= expr",
+ /* 226 */ "case_operand ::=",
+ /* 227 */ "exprlist ::=",
+ /* 228 */ "nexprlist ::= nexprlist COMMA expr",
+ /* 229 */ "nexprlist ::= expr",
+ /* 230 */ "paren_exprlist ::=",
+ /* 231 */ "paren_exprlist ::= LP exprlist RP",
+ /* 232 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt",
+ /* 233 */ "uniqueflag ::= UNIQUE",
+ /* 234 */ "uniqueflag ::=",
+ /* 235 */ "eidlist_opt ::=",
+ /* 236 */ "eidlist_opt ::= LP eidlist RP",
+ /* 237 */ "eidlist ::= eidlist COMMA nm collate sortorder",
+ /* 238 */ "eidlist ::= nm collate sortorder",
+ /* 239 */ "collate ::=",
+ /* 240 */ "collate ::= COLLATE ID|STRING",
+ /* 241 */ "cmd ::= DROP INDEX ifexists fullname",
+ /* 242 */ "cmd ::= VACUUM vinto",
+ /* 243 */ "cmd ::= VACUUM nm vinto",
+ /* 244 */ "vinto ::= INTO expr",
+ /* 245 */ "vinto ::=",
+ /* 246 */ "cmd ::= PRAGMA nm dbnm",
+ /* 247 */ "cmd ::= PRAGMA nm dbnm EQ nmnum",
+ /* 248 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP",
+ /* 249 */ "cmd ::= PRAGMA nm dbnm EQ minus_num",
+ /* 250 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP",
+ /* 251 */ "plus_num ::= PLUS INTEGER|FLOAT",
+ /* 252 */ "minus_num ::= MINUS INTEGER|FLOAT",
+ /* 253 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END",
+ /* 254 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause",
+ /* 255 */ "trigger_time ::= BEFORE|AFTER",
+ /* 256 */ "trigger_time ::= INSTEAD OF",
+ /* 257 */ "trigger_time ::=",
+ /* 258 */ "trigger_event ::= DELETE|INSERT",
+ /* 259 */ "trigger_event ::= UPDATE",
+ /* 260 */ "trigger_event ::= UPDATE OF idlist",
+ /* 261 */ "when_clause ::=",
+ /* 262 */ "when_clause ::= WHEN expr",
+ /* 263 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI",
+ /* 264 */ "trigger_cmd_list ::= trigger_cmd SEMI",
+ /* 265 */ "trnm ::= nm DOT nm",
+ /* 266 */ "tridxby ::= INDEXED BY nm",
+ /* 267 */ "tridxby ::= NOT INDEXED",
+ /* 268 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt",
+ /* 269 */ "trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt",
+ /* 270 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt",
+ /* 271 */ "trigger_cmd ::= scanpt select scanpt",
+ /* 272 */ "expr ::= RAISE LP IGNORE RP",
+ /* 273 */ "expr ::= RAISE LP raisetype COMMA nm RP",
+ /* 274 */ "raisetype ::= ROLLBACK",
+ /* 275 */ "raisetype ::= ABORT",
+ /* 276 */ "raisetype ::= FAIL",
+ /* 277 */ "cmd ::= DROP TRIGGER ifexists fullname",
+ /* 278 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt",
+ /* 279 */ "cmd ::= DETACH database_kw_opt expr",
+ /* 280 */ "key_opt ::=",
+ /* 281 */ "key_opt ::= KEY expr",
+ /* 282 */ "cmd ::= REINDEX",
+ /* 283 */ "cmd ::= REINDEX nm dbnm",
+ /* 284 */ "cmd ::= ANALYZE",
+ /* 285 */ "cmd ::= ANALYZE nm dbnm",
+ /* 286 */ "cmd ::= ALTER TABLE fullname RENAME TO nm",
+ /* 287 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist",
+ /* 288 */ "cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm",
+ /* 289 */ "add_column_fullname ::= fullname",
+ /* 290 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm",
+ /* 291 */ "cmd ::= create_vtab",
+ /* 292 */ "cmd ::= create_vtab LP vtabarglist RP",
+ /* 293 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm",
+ /* 294 */ "vtabarg ::=",
+ /* 295 */ "vtabargtoken ::= ANY",
+ /* 296 */ "vtabargtoken ::= lp anylist RP",
+ /* 297 */ "lp ::= LP",
+ /* 298 */ "with ::= WITH wqlist",
+ /* 299 */ "with ::= WITH RECURSIVE wqlist",
+ /* 300 */ "wqas ::= AS",
+ /* 301 */ "wqas ::= AS MATERIALIZED",
+ /* 302 */ "wqas ::= AS NOT MATERIALIZED",
+ /* 303 */ "wqitem ::= nm eidlist_opt wqas LP select RP",
+ /* 304 */ "wqlist ::= wqitem",
+ /* 305 */ "wqlist ::= wqlist COMMA wqitem",
+ /* 306 */ "windowdefn_list ::= windowdefn",
+ /* 307 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn",
+ /* 308 */ "windowdefn ::= nm AS LP window RP",
+ /* 309 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt",
+ /* 310 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt",
+ /* 311 */ "window ::= ORDER BY sortlist frame_opt",
+ /* 312 */ "window ::= nm ORDER BY sortlist frame_opt",
+ /* 313 */ "window ::= frame_opt",
+ /* 314 */ "window ::= nm frame_opt",
+ /* 315 */ "frame_opt ::=",
+ /* 316 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt",
+ /* 317 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt",
+ /* 318 */ "range_or_rows ::= RANGE|ROWS|GROUPS",
+ /* 319 */ "frame_bound_s ::= frame_bound",
+ /* 320 */ "frame_bound_s ::= UNBOUNDED PRECEDING",
+ /* 321 */ "frame_bound_e ::= frame_bound",
+ /* 322 */ "frame_bound_e ::= UNBOUNDED FOLLOWING",
+ /* 323 */ "frame_bound ::= expr PRECEDING|FOLLOWING",
+ /* 324 */ "frame_bound ::= CURRENT ROW",
+ /* 325 */ "frame_exclude_opt ::=",
+ /* 326 */ "frame_exclude_opt ::= EXCLUDE frame_exclude",
+ /* 327 */ "frame_exclude ::= NO OTHERS",
+ /* 328 */ "frame_exclude ::= CURRENT ROW",
+ /* 329 */ "frame_exclude ::= GROUP|TIES",
+ /* 330 */ "window_clause ::= WINDOW windowdefn_list",
+ /* 331 */ "filter_over ::= filter_clause over_clause",
+ /* 332 */ "filter_over ::= over_clause",
+ /* 333 */ "filter_over ::= filter_clause",
+ /* 334 */ "over_clause ::= OVER LP window RP",
+ /* 335 */ "over_clause ::= OVER nm",
+ /* 336 */ "filter_clause ::= FILTER LP WHERE expr RP",
+ /* 337 */ "input ::= cmdlist",
+ /* 338 */ "cmdlist ::= cmdlist ecmd",
+ /* 339 */ "cmdlist ::= ecmd",
+ /* 340 */ "ecmd ::= SEMI",
+ /* 341 */ "ecmd ::= cmdx SEMI",
+ /* 342 */ "ecmd ::= explain cmdx SEMI",
+ /* 343 */ "trans_opt ::=",
+ /* 344 */ "trans_opt ::= TRANSACTION",
+ /* 345 */ "trans_opt ::= TRANSACTION nm",
+ /* 346 */ "savepoint_opt ::= SAVEPOINT",
+ /* 347 */ "savepoint_opt ::=",
+ /* 348 */ "cmd ::= create_table create_table_args",
+ /* 349 */ "columnlist ::= columnlist COMMA columnname carglist",
+ /* 350 */ "columnlist ::= columnname carglist",
+ /* 351 */ "nm ::= ID|INDEXED",
+ /* 352 */ "nm ::= STRING",
+ /* 353 */ "nm ::= JOIN_KW",
+ /* 354 */ "typetoken ::= typename",
+ /* 355 */ "typename ::= ID|STRING",
+ /* 356 */ "signed ::= plus_num",
+ /* 357 */ "signed ::= minus_num",
+ /* 358 */ "carglist ::= carglist ccons",
+ /* 359 */ "carglist ::=",
+ /* 360 */ "ccons ::= NULL onconf",
+ /* 361 */ "ccons ::= GENERATED ALWAYS AS generated",
+ /* 362 */ "ccons ::= AS generated",
+ /* 363 */ "conslist_opt ::= COMMA conslist",
+ /* 364 */ "conslist ::= conslist tconscomma tcons",
+ /* 365 */ "conslist ::= tcons",
+ /* 366 */ "tconscomma ::=",
+ /* 367 */ "defer_subclause_opt ::= defer_subclause",
+ /* 368 */ "resolvetype ::= raisetype",
+ /* 369 */ "selectnowith ::= oneselect",
+ /* 370 */ "oneselect ::= values",
+ /* 371 */ "sclp ::= selcollist COMMA",
+ /* 372 */ "as ::= ID|STRING",
+ /* 373 */ "returning ::=",
+ /* 374 */ "expr ::= term",
+ /* 375 */ "likeop ::= LIKE_KW|MATCH",
+ /* 376 */ "exprlist ::= nexprlist",
+ /* 377 */ "nmnum ::= plus_num",
+ /* 378 */ "nmnum ::= nm",
+ /* 379 */ "nmnum ::= ON",
+ /* 380 */ "nmnum ::= DELETE",
+ /* 381 */ "nmnum ::= DEFAULT",
+ /* 382 */ "plus_num ::= INTEGER|FLOAT",
+ /* 383 */ "foreach_clause ::=",
+ /* 384 */ "foreach_clause ::= FOR EACH ROW",
+ /* 385 */ "trnm ::= nm",
+ /* 386 */ "tridxby ::=",
+ /* 387 */ "database_kw_opt ::= DATABASE",
+ /* 388 */ "database_kw_opt ::=",
+ /* 389 */ "kwcolumn_opt ::=",
+ /* 390 */ "kwcolumn_opt ::= COLUMNKW",
+ /* 391 */ "vtabarglist ::= vtabarg",
+ /* 392 */ "vtabarglist ::= vtabarglist COMMA vtabarg",
+ /* 393 */ "vtabarg ::= vtabarg vtabargtoken",
+ /* 394 */ "anylist ::=",
+ /* 395 */ "anylist ::= anylist LP anylist RP",
+ /* 396 */ "anylist ::= anylist ANY",
+ /* 397 */ "with ::=",
};
#endif /* NDEBUG */
@@ -156289,98 +160317,99 @@ static void yy_destructor(
** inside the C code.
*/
/********* Begin destructor definitions ***************************************/
- case 200: /* select */
- case 234: /* selectnowith */
- case 235: /* oneselect */
- case 247: /* values */
+ case 203: /* select */
+ case 237: /* selectnowith */
+ case 238: /* oneselect */
+ case 250: /* values */
{
-sqlite3SelectDelete(pParse->db, (yypminor->yy539));
-}
- break;
- case 211: /* term */
- case 212: /* expr */
- case 241: /* where_opt */
- case 243: /* having_opt */
- case 255: /* on_opt */
- case 271: /* case_operand */
- case 273: /* case_else */
- case 276: /* vinto */
- case 283: /* when_clause */
- case 288: /* key_opt */
- case 302: /* filter_clause */
+sqlite3SelectDelete(pParse->db, (yypminor->yy81));
+}
+ break;
+ case 214: /* term */
+ case 215: /* expr */
+ case 244: /* where_opt */
+ case 246: /* having_opt */
+ case 258: /* on_opt */
+ case 265: /* where_opt_ret */
+ case 276: /* case_operand */
+ case 278: /* case_else */
+ case 281: /* vinto */
+ case 288: /* when_clause */
+ case 293: /* key_opt */
+ case 309: /* filter_clause */
{
-sqlite3ExprDelete(pParse->db, (yypminor->yy202));
-}
- break;
- case 216: /* eidlist_opt */
- case 226: /* sortlist */
- case 227: /* eidlist */
- case 239: /* selcollist */
- case 242: /* groupby_opt */
- case 244: /* orderby_opt */
- case 248: /* nexprlist */
- case 249: /* sclp */
- case 257: /* exprlist */
- case 262: /* setlist */
- case 270: /* paren_exprlist */
- case 272: /* case_exprlist */
- case 301: /* part_opt */
+sqlite3ExprDelete(pParse->db, (yypminor->yy404));
+}
+ break;
+ case 219: /* eidlist_opt */
+ case 229: /* sortlist */
+ case 230: /* eidlist */
+ case 242: /* selcollist */
+ case 245: /* groupby_opt */
+ case 247: /* orderby_opt */
+ case 251: /* nexprlist */
+ case 252: /* sclp */
+ case 260: /* exprlist */
+ case 266: /* setlist */
+ case 275: /* paren_exprlist */
+ case 277: /* case_exprlist */
+ case 308: /* part_opt */
{
-sqlite3ExprListDelete(pParse->db, (yypminor->yy242));
+sqlite3ExprListDelete(pParse->db, (yypminor->yy70));
}
break;
- case 233: /* fullname */
- case 240: /* from */
- case 251: /* seltablist */
- case 252: /* stl_prefix */
- case 258: /* xfullname */
+ case 236: /* fullname */
+ case 243: /* from */
+ case 254: /* seltablist */
+ case 255: /* stl_prefix */
+ case 261: /* xfullname */
{
-sqlite3SrcListDelete(pParse->db, (yypminor->yy47));
+sqlite3SrcListDelete(pParse->db, (yypminor->yy153));
}
break;
- case 236: /* wqlist */
+ case 239: /* wqlist */
{
-sqlite3WithDelete(pParse->db, (yypminor->yy131));
+sqlite3WithDelete(pParse->db, (yypminor->yy103));
}
break;
- case 246: /* window_clause */
- case 297: /* windowdefn_list */
+ case 249: /* window_clause */
+ case 304: /* windowdefn_list */
{
-sqlite3WindowListDelete(pParse->db, (yypminor->yy303));
+sqlite3WindowListDelete(pParse->db, (yypminor->yy49));
}
break;
- case 256: /* using_opt */
- case 259: /* idlist */
- case 264: /* idlist_opt */
+ case 259: /* using_opt */
+ case 262: /* idlist */
+ case 268: /* idlist_opt */
{
-sqlite3IdListDelete(pParse->db, (yypminor->yy600));
+sqlite3IdListDelete(pParse->db, (yypminor->yy436));
}
break;
- case 266: /* filter_over */
- case 298: /* windowdefn */
- case 299: /* window */
- case 300: /* frame_opt */
- case 303: /* over_clause */
+ case 271: /* filter_over */
+ case 305: /* windowdefn */
+ case 306: /* window */
+ case 307: /* frame_opt */
+ case 310: /* over_clause */
{
-sqlite3WindowDelete(pParse->db, (yypminor->yy303));
+sqlite3WindowDelete(pParse->db, (yypminor->yy49));
}
break;
- case 279: /* trigger_cmd_list */
- case 284: /* trigger_cmd */
+ case 284: /* trigger_cmd_list */
+ case 289: /* trigger_cmd */
{
-sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy447));
+sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy157));
}
break;
- case 281: /* trigger_event */
+ case 286: /* trigger_event */
{
-sqlite3IdListDelete(pParse->db, (yypminor->yy230).b);
+sqlite3IdListDelete(pParse->db, (yypminor->yy262).b);
}
break;
- case 305: /* frame_bound */
- case 306: /* frame_bound_s */
- case 307: /* frame_bound_e */
+ case 312: /* frame_bound */
+ case 313: /* frame_bound_s */
+ case 314: /* frame_bound_e */
{
-sqlite3ExprDelete(pParse->db, (yypminor->yy77).pExpr);
+sqlite3ExprDelete(pParse->db, (yypminor->yy117).pExpr);
}
break;
/********* End destructor definitions *****************************************/
@@ -156547,7 +160576,7 @@ static YYACTIONTYPE yy_find_shift_action(
#endif /* YYWILDCARD */
return yy_default[stateno];
}else{
- assert( i>=0 && i<sizeof(yy_action)/sizeof(yy_action[0]) );
+ assert( i>=0 && i<(int)(sizeof(yy_action)/sizeof(yy_action[0])) );
return yy_action[i];
}
}while(1);
@@ -156671,391 +160700,404 @@ static void yy_shift(
/* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side
** of that rule */
static const YYCODETYPE yyRuleInfoLhs[] = {
- 185, /* (0) explain ::= EXPLAIN */
- 185, /* (1) explain ::= EXPLAIN QUERY PLAN */
- 184, /* (2) cmdx ::= cmd */
- 186, /* (3) cmd ::= BEGIN transtype trans_opt */
- 187, /* (4) transtype ::= */
- 187, /* (5) transtype ::= DEFERRED */
- 187, /* (6) transtype ::= IMMEDIATE */
- 187, /* (7) transtype ::= EXCLUSIVE */
- 186, /* (8) cmd ::= COMMIT|END trans_opt */
- 186, /* (9) cmd ::= ROLLBACK trans_opt */
- 186, /* (10) cmd ::= SAVEPOINT nm */
- 186, /* (11) cmd ::= RELEASE savepoint_opt nm */
- 186, /* (12) cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */
- 191, /* (13) create_table ::= createkw temp TABLE ifnotexists nm dbnm */
- 193, /* (14) createkw ::= CREATE */
- 195, /* (15) ifnotexists ::= */
- 195, /* (16) ifnotexists ::= IF NOT EXISTS */
- 194, /* (17) temp ::= TEMP */
- 194, /* (18) temp ::= */
- 192, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_options */
- 192, /* (20) create_table_args ::= AS select */
- 199, /* (21) table_options ::= */
- 199, /* (22) table_options ::= WITHOUT nm */
- 201, /* (23) columnname ::= nm typetoken */
- 203, /* (24) typetoken ::= */
- 203, /* (25) typetoken ::= typename LP signed RP */
- 203, /* (26) typetoken ::= typename LP signed COMMA signed RP */
- 204, /* (27) typename ::= typename ID|STRING */
- 208, /* (28) scanpt ::= */
- 209, /* (29) scantok ::= */
- 210, /* (30) ccons ::= CONSTRAINT nm */
- 210, /* (31) ccons ::= DEFAULT scantok term */
- 210, /* (32) ccons ::= DEFAULT LP expr RP */
- 210, /* (33) ccons ::= DEFAULT PLUS scantok term */
- 210, /* (34) ccons ::= DEFAULT MINUS scantok term */
- 210, /* (35) ccons ::= DEFAULT scantok ID|INDEXED */
- 210, /* (36) ccons ::= NOT NULL onconf */
- 210, /* (37) ccons ::= PRIMARY KEY sortorder onconf autoinc */
- 210, /* (38) ccons ::= UNIQUE onconf */
- 210, /* (39) ccons ::= CHECK LP expr RP */
- 210, /* (40) ccons ::= REFERENCES nm eidlist_opt refargs */
- 210, /* (41) ccons ::= defer_subclause */
- 210, /* (42) ccons ::= COLLATE ID|STRING */
- 219, /* (43) generated ::= LP expr RP */
- 219, /* (44) generated ::= LP expr RP ID */
- 215, /* (45) autoinc ::= */
- 215, /* (46) autoinc ::= AUTOINCR */
- 217, /* (47) refargs ::= */
- 217, /* (48) refargs ::= refargs refarg */
- 220, /* (49) refarg ::= MATCH nm */
- 220, /* (50) refarg ::= ON INSERT refact */
- 220, /* (51) refarg ::= ON DELETE refact */
- 220, /* (52) refarg ::= ON UPDATE refact */
- 221, /* (53) refact ::= SET NULL */
- 221, /* (54) refact ::= SET DEFAULT */
- 221, /* (55) refact ::= CASCADE */
- 221, /* (56) refact ::= RESTRICT */
- 221, /* (57) refact ::= NO ACTION */
- 218, /* (58) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */
- 218, /* (59) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */
- 222, /* (60) init_deferred_pred_opt ::= */
- 222, /* (61) init_deferred_pred_opt ::= INITIALLY DEFERRED */
- 222, /* (62) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */
- 198, /* (63) conslist_opt ::= */
- 224, /* (64) tconscomma ::= COMMA */
- 225, /* (65) tcons ::= CONSTRAINT nm */
- 225, /* (66) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */
- 225, /* (67) tcons ::= UNIQUE LP sortlist RP onconf */
- 225, /* (68) tcons ::= CHECK LP expr RP onconf */
- 225, /* (69) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */
- 228, /* (70) defer_subclause_opt ::= */
- 213, /* (71) onconf ::= */
- 213, /* (72) onconf ::= ON CONFLICT resolvetype */
- 229, /* (73) orconf ::= */
- 229, /* (74) orconf ::= OR resolvetype */
- 230, /* (75) resolvetype ::= IGNORE */
- 230, /* (76) resolvetype ::= REPLACE */
- 186, /* (77) cmd ::= DROP TABLE ifexists fullname */
- 232, /* (78) ifexists ::= IF EXISTS */
- 232, /* (79) ifexists ::= */
- 186, /* (80) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */
- 186, /* (81) cmd ::= DROP VIEW ifexists fullname */
- 186, /* (82) cmd ::= select */
- 200, /* (83) select ::= WITH wqlist selectnowith */
- 200, /* (84) select ::= WITH RECURSIVE wqlist selectnowith */
- 200, /* (85) select ::= selectnowith */
- 234, /* (86) selectnowith ::= selectnowith multiselect_op oneselect */
- 237, /* (87) multiselect_op ::= UNION */
- 237, /* (88) multiselect_op ::= UNION ALL */
- 237, /* (89) multiselect_op ::= EXCEPT|INTERSECT */
- 235, /* (90) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */
- 235, /* (91) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */
- 247, /* (92) values ::= VALUES LP nexprlist RP */
- 247, /* (93) values ::= values COMMA LP nexprlist RP */
- 238, /* (94) distinct ::= DISTINCT */
- 238, /* (95) distinct ::= ALL */
- 238, /* (96) distinct ::= */
- 249, /* (97) sclp ::= */
- 239, /* (98) selcollist ::= sclp scanpt expr scanpt as */
- 239, /* (99) selcollist ::= sclp scanpt STAR */
- 239, /* (100) selcollist ::= sclp scanpt nm DOT STAR */
- 250, /* (101) as ::= AS nm */
- 250, /* (102) as ::= */
- 240, /* (103) from ::= */
- 240, /* (104) from ::= FROM seltablist */
- 252, /* (105) stl_prefix ::= seltablist joinop */
- 252, /* (106) stl_prefix ::= */
- 251, /* (107) seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */
- 251, /* (108) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */
- 251, /* (109) seltablist ::= stl_prefix LP select RP as on_opt using_opt */
- 251, /* (110) seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */
- 196, /* (111) dbnm ::= */
- 196, /* (112) dbnm ::= DOT nm */
- 233, /* (113) fullname ::= nm */
- 233, /* (114) fullname ::= nm DOT nm */
- 258, /* (115) xfullname ::= nm */
- 258, /* (116) xfullname ::= nm DOT nm */
- 258, /* (117) xfullname ::= nm DOT nm AS nm */
- 258, /* (118) xfullname ::= nm AS nm */
- 253, /* (119) joinop ::= COMMA|JOIN */
- 253, /* (120) joinop ::= JOIN_KW JOIN */
- 253, /* (121) joinop ::= JOIN_KW nm JOIN */
- 253, /* (122) joinop ::= JOIN_KW nm nm JOIN */
- 255, /* (123) on_opt ::= ON expr */
- 255, /* (124) on_opt ::= */
- 254, /* (125) indexed_opt ::= */
- 254, /* (126) indexed_opt ::= INDEXED BY nm */
- 254, /* (127) indexed_opt ::= NOT INDEXED */
- 256, /* (128) using_opt ::= USING LP idlist RP */
- 256, /* (129) using_opt ::= */
- 244, /* (130) orderby_opt ::= */
- 244, /* (131) orderby_opt ::= ORDER BY sortlist */
- 226, /* (132) sortlist ::= sortlist COMMA expr sortorder nulls */
- 226, /* (133) sortlist ::= expr sortorder nulls */
- 214, /* (134) sortorder ::= ASC */
- 214, /* (135) sortorder ::= DESC */
- 214, /* (136) sortorder ::= */
- 260, /* (137) nulls ::= NULLS FIRST */
- 260, /* (138) nulls ::= NULLS LAST */
- 260, /* (139) nulls ::= */
- 242, /* (140) groupby_opt ::= */
- 242, /* (141) groupby_opt ::= GROUP BY nexprlist */
- 243, /* (142) having_opt ::= */
- 243, /* (143) having_opt ::= HAVING expr */
- 245, /* (144) limit_opt ::= */
- 245, /* (145) limit_opt ::= LIMIT expr */
- 245, /* (146) limit_opt ::= LIMIT expr OFFSET expr */
- 245, /* (147) limit_opt ::= LIMIT expr COMMA expr */
- 186, /* (148) cmd ::= with DELETE FROM xfullname indexed_opt where_opt */
- 241, /* (149) where_opt ::= */
- 241, /* (150) where_opt ::= WHERE expr */
- 186, /* (151) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt */
- 262, /* (152) setlist ::= setlist COMMA nm EQ expr */
- 262, /* (153) setlist ::= setlist COMMA LP idlist RP EQ expr */
- 262, /* (154) setlist ::= nm EQ expr */
- 262, /* (155) setlist ::= LP idlist RP EQ expr */
- 186, /* (156) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */
- 186, /* (157) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES */
- 265, /* (158) upsert ::= */
- 265, /* (159) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt */
- 265, /* (160) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING */
- 265, /* (161) upsert ::= ON CONFLICT DO NOTHING */
- 263, /* (162) insert_cmd ::= INSERT orconf */
- 263, /* (163) insert_cmd ::= REPLACE */
- 264, /* (164) idlist_opt ::= */
- 264, /* (165) idlist_opt ::= LP idlist RP */
- 259, /* (166) idlist ::= idlist COMMA nm */
- 259, /* (167) idlist ::= nm */
- 212, /* (168) expr ::= LP expr RP */
- 212, /* (169) expr ::= ID|INDEXED */
- 212, /* (170) expr ::= JOIN_KW */
- 212, /* (171) expr ::= nm DOT nm */
- 212, /* (172) expr ::= nm DOT nm DOT nm */
- 211, /* (173) term ::= NULL|FLOAT|BLOB */
- 211, /* (174) term ::= STRING */
- 211, /* (175) term ::= INTEGER */
- 212, /* (176) expr ::= VARIABLE */
- 212, /* (177) expr ::= expr COLLATE ID|STRING */
- 212, /* (178) expr ::= CAST LP expr AS typetoken RP */
- 212, /* (179) expr ::= ID|INDEXED LP distinct exprlist RP */
- 212, /* (180) expr ::= ID|INDEXED LP STAR RP */
- 212, /* (181) expr ::= ID|INDEXED LP distinct exprlist RP filter_over */
- 212, /* (182) expr ::= ID|INDEXED LP STAR RP filter_over */
- 211, /* (183) term ::= CTIME_KW */
- 212, /* (184) expr ::= LP nexprlist COMMA expr RP */
- 212, /* (185) expr ::= expr AND expr */
- 212, /* (186) expr ::= expr OR expr */
- 212, /* (187) expr ::= expr LT|GT|GE|LE expr */
- 212, /* (188) expr ::= expr EQ|NE expr */
- 212, /* (189) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */
- 212, /* (190) expr ::= expr PLUS|MINUS expr */
- 212, /* (191) expr ::= expr STAR|SLASH|REM expr */
- 212, /* (192) expr ::= expr CONCAT expr */
- 267, /* (193) likeop ::= NOT LIKE_KW|MATCH */
- 212, /* (194) expr ::= expr likeop expr */
- 212, /* (195) expr ::= expr likeop expr ESCAPE expr */
- 212, /* (196) expr ::= expr ISNULL|NOTNULL */
- 212, /* (197) expr ::= expr NOT NULL */
- 212, /* (198) expr ::= expr IS expr */
- 212, /* (199) expr ::= expr IS NOT expr */
- 212, /* (200) expr ::= NOT expr */
- 212, /* (201) expr ::= BITNOT expr */
- 212, /* (202) expr ::= PLUS|MINUS expr */
- 268, /* (203) between_op ::= BETWEEN */
- 268, /* (204) between_op ::= NOT BETWEEN */
- 212, /* (205) expr ::= expr between_op expr AND expr */
- 269, /* (206) in_op ::= IN */
- 269, /* (207) in_op ::= NOT IN */
- 212, /* (208) expr ::= expr in_op LP exprlist RP */
- 212, /* (209) expr ::= LP select RP */
- 212, /* (210) expr ::= expr in_op LP select RP */
- 212, /* (211) expr ::= expr in_op nm dbnm paren_exprlist */
- 212, /* (212) expr ::= EXISTS LP select RP */
- 212, /* (213) expr ::= CASE case_operand case_exprlist case_else END */
- 272, /* (214) case_exprlist ::= case_exprlist WHEN expr THEN expr */
- 272, /* (215) case_exprlist ::= WHEN expr THEN expr */
- 273, /* (216) case_else ::= ELSE expr */
- 273, /* (217) case_else ::= */
- 271, /* (218) case_operand ::= expr */
- 271, /* (219) case_operand ::= */
- 257, /* (220) exprlist ::= */
- 248, /* (221) nexprlist ::= nexprlist COMMA expr */
- 248, /* (222) nexprlist ::= expr */
- 270, /* (223) paren_exprlist ::= */
- 270, /* (224) paren_exprlist ::= LP exprlist RP */
- 186, /* (225) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
- 274, /* (226) uniqueflag ::= UNIQUE */
- 274, /* (227) uniqueflag ::= */
- 216, /* (228) eidlist_opt ::= */
- 216, /* (229) eidlist_opt ::= LP eidlist RP */
- 227, /* (230) eidlist ::= eidlist COMMA nm collate sortorder */
- 227, /* (231) eidlist ::= nm collate sortorder */
- 275, /* (232) collate ::= */
- 275, /* (233) collate ::= COLLATE ID|STRING */
- 186, /* (234) cmd ::= DROP INDEX ifexists fullname */
- 186, /* (235) cmd ::= VACUUM vinto */
- 186, /* (236) cmd ::= VACUUM nm vinto */
- 276, /* (237) vinto ::= INTO expr */
- 276, /* (238) vinto ::= */
- 186, /* (239) cmd ::= PRAGMA nm dbnm */
- 186, /* (240) cmd ::= PRAGMA nm dbnm EQ nmnum */
- 186, /* (241) cmd ::= PRAGMA nm dbnm LP nmnum RP */
- 186, /* (242) cmd ::= PRAGMA nm dbnm EQ minus_num */
- 186, /* (243) cmd ::= PRAGMA nm dbnm LP minus_num RP */
- 206, /* (244) plus_num ::= PLUS INTEGER|FLOAT */
- 207, /* (245) minus_num ::= MINUS INTEGER|FLOAT */
- 186, /* (246) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
- 278, /* (247) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
- 280, /* (248) trigger_time ::= BEFORE|AFTER */
- 280, /* (249) trigger_time ::= INSTEAD OF */
- 280, /* (250) trigger_time ::= */
- 281, /* (251) trigger_event ::= DELETE|INSERT */
- 281, /* (252) trigger_event ::= UPDATE */
- 281, /* (253) trigger_event ::= UPDATE OF idlist */
- 283, /* (254) when_clause ::= */
- 283, /* (255) when_clause ::= WHEN expr */
- 279, /* (256) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
- 279, /* (257) trigger_cmd_list ::= trigger_cmd SEMI */
- 285, /* (258) trnm ::= nm DOT nm */
- 286, /* (259) tridxby ::= INDEXED BY nm */
- 286, /* (260) tridxby ::= NOT INDEXED */
- 284, /* (261) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
- 284, /* (262) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
- 284, /* (263) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
- 284, /* (264) trigger_cmd ::= scanpt select scanpt */
- 212, /* (265) expr ::= RAISE LP IGNORE RP */
- 212, /* (266) expr ::= RAISE LP raisetype COMMA nm RP */
- 231, /* (267) raisetype ::= ROLLBACK */
- 231, /* (268) raisetype ::= ABORT */
- 231, /* (269) raisetype ::= FAIL */
- 186, /* (270) cmd ::= DROP TRIGGER ifexists fullname */
- 186, /* (271) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
- 186, /* (272) cmd ::= DETACH database_kw_opt expr */
- 288, /* (273) key_opt ::= */
- 288, /* (274) key_opt ::= KEY expr */
- 186, /* (275) cmd ::= REINDEX */
- 186, /* (276) cmd ::= REINDEX nm dbnm */
- 186, /* (277) cmd ::= ANALYZE */
- 186, /* (278) cmd ::= ANALYZE nm dbnm */
- 186, /* (279) cmd ::= ALTER TABLE fullname RENAME TO nm */
- 186, /* (280) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
- 289, /* (281) add_column_fullname ::= fullname */
- 186, /* (282) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
- 186, /* (283) cmd ::= create_vtab */
- 186, /* (284) cmd ::= create_vtab LP vtabarglist RP */
- 291, /* (285) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
- 293, /* (286) vtabarg ::= */
- 294, /* (287) vtabargtoken ::= ANY */
- 294, /* (288) vtabargtoken ::= lp anylist RP */
- 295, /* (289) lp ::= LP */
- 261, /* (290) with ::= WITH wqlist */
- 261, /* (291) with ::= WITH RECURSIVE wqlist */
- 236, /* (292) wqlist ::= nm eidlist_opt AS LP select RP */
- 236, /* (293) wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */
- 297, /* (294) windowdefn_list ::= windowdefn */
- 297, /* (295) windowdefn_list ::= windowdefn_list COMMA windowdefn */
- 298, /* (296) windowdefn ::= nm AS LP window RP */
- 299, /* (297) window ::= PARTITION BY nexprlist orderby_opt frame_opt */
- 299, /* (298) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
- 299, /* (299) window ::= ORDER BY sortlist frame_opt */
- 299, /* (300) window ::= nm ORDER BY sortlist frame_opt */
- 299, /* (301) window ::= frame_opt */
- 299, /* (302) window ::= nm frame_opt */
- 300, /* (303) frame_opt ::= */
- 300, /* (304) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
- 300, /* (305) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
- 304, /* (306) range_or_rows ::= RANGE|ROWS|GROUPS */
- 306, /* (307) frame_bound_s ::= frame_bound */
- 306, /* (308) frame_bound_s ::= UNBOUNDED PRECEDING */
- 307, /* (309) frame_bound_e ::= frame_bound */
- 307, /* (310) frame_bound_e ::= UNBOUNDED FOLLOWING */
- 305, /* (311) frame_bound ::= expr PRECEDING|FOLLOWING */
- 305, /* (312) frame_bound ::= CURRENT ROW */
- 308, /* (313) frame_exclude_opt ::= */
- 308, /* (314) frame_exclude_opt ::= EXCLUDE frame_exclude */
- 309, /* (315) frame_exclude ::= NO OTHERS */
- 309, /* (316) frame_exclude ::= CURRENT ROW */
- 309, /* (317) frame_exclude ::= GROUP|TIES */
- 246, /* (318) window_clause ::= WINDOW windowdefn_list */
- 266, /* (319) filter_over ::= filter_clause over_clause */
- 266, /* (320) filter_over ::= over_clause */
- 266, /* (321) filter_over ::= filter_clause */
- 303, /* (322) over_clause ::= OVER LP window RP */
- 303, /* (323) over_clause ::= OVER nm */
- 302, /* (324) filter_clause ::= FILTER LP WHERE expr RP */
- 181, /* (325) input ::= cmdlist */
- 182, /* (326) cmdlist ::= cmdlist ecmd */
- 182, /* (327) cmdlist ::= ecmd */
- 183, /* (328) ecmd ::= SEMI */
- 183, /* (329) ecmd ::= cmdx SEMI */
- 183, /* (330) ecmd ::= explain cmdx SEMI */
- 188, /* (331) trans_opt ::= */
- 188, /* (332) trans_opt ::= TRANSACTION */
- 188, /* (333) trans_opt ::= TRANSACTION nm */
- 190, /* (334) savepoint_opt ::= SAVEPOINT */
- 190, /* (335) savepoint_opt ::= */
- 186, /* (336) cmd ::= create_table create_table_args */
- 197, /* (337) columnlist ::= columnlist COMMA columnname carglist */
- 197, /* (338) columnlist ::= columnname carglist */
- 189, /* (339) nm ::= ID|INDEXED */
- 189, /* (340) nm ::= STRING */
- 189, /* (341) nm ::= JOIN_KW */
- 203, /* (342) typetoken ::= typename */
- 204, /* (343) typename ::= ID|STRING */
- 205, /* (344) signed ::= plus_num */
- 205, /* (345) signed ::= minus_num */
- 202, /* (346) carglist ::= carglist ccons */
- 202, /* (347) carglist ::= */
- 210, /* (348) ccons ::= NULL onconf */
- 210, /* (349) ccons ::= GENERATED ALWAYS AS generated */
- 210, /* (350) ccons ::= AS generated */
- 198, /* (351) conslist_opt ::= COMMA conslist */
- 223, /* (352) conslist ::= conslist tconscomma tcons */
- 223, /* (353) conslist ::= tcons */
- 224, /* (354) tconscomma ::= */
- 228, /* (355) defer_subclause_opt ::= defer_subclause */
- 230, /* (356) resolvetype ::= raisetype */
- 234, /* (357) selectnowith ::= oneselect */
- 235, /* (358) oneselect ::= values */
- 249, /* (359) sclp ::= selcollist COMMA */
- 250, /* (360) as ::= ID|STRING */
- 212, /* (361) expr ::= term */
- 267, /* (362) likeop ::= LIKE_KW|MATCH */
- 257, /* (363) exprlist ::= nexprlist */
- 277, /* (364) nmnum ::= plus_num */
- 277, /* (365) nmnum ::= nm */
- 277, /* (366) nmnum ::= ON */
- 277, /* (367) nmnum ::= DELETE */
- 277, /* (368) nmnum ::= DEFAULT */
- 206, /* (369) plus_num ::= INTEGER|FLOAT */
- 282, /* (370) foreach_clause ::= */
- 282, /* (371) foreach_clause ::= FOR EACH ROW */
- 285, /* (372) trnm ::= nm */
- 286, /* (373) tridxby ::= */
- 287, /* (374) database_kw_opt ::= DATABASE */
- 287, /* (375) database_kw_opt ::= */
- 290, /* (376) kwcolumn_opt ::= */
- 290, /* (377) kwcolumn_opt ::= COLUMNKW */
- 292, /* (378) vtabarglist ::= vtabarg */
- 292, /* (379) vtabarglist ::= vtabarglist COMMA vtabarg */
- 293, /* (380) vtabarg ::= vtabarg vtabargtoken */
- 296, /* (381) anylist ::= */
- 296, /* (382) anylist ::= anylist LP anylist RP */
- 296, /* (383) anylist ::= anylist ANY */
- 261, /* (384) with ::= */
+ 188, /* (0) explain ::= EXPLAIN */
+ 188, /* (1) explain ::= EXPLAIN QUERY PLAN */
+ 187, /* (2) cmdx ::= cmd */
+ 189, /* (3) cmd ::= BEGIN transtype trans_opt */
+ 190, /* (4) transtype ::= */
+ 190, /* (5) transtype ::= DEFERRED */
+ 190, /* (6) transtype ::= IMMEDIATE */
+ 190, /* (7) transtype ::= EXCLUSIVE */
+ 189, /* (8) cmd ::= COMMIT|END trans_opt */
+ 189, /* (9) cmd ::= ROLLBACK trans_opt */
+ 189, /* (10) cmd ::= SAVEPOINT nm */
+ 189, /* (11) cmd ::= RELEASE savepoint_opt nm */
+ 189, /* (12) cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */
+ 194, /* (13) create_table ::= createkw temp TABLE ifnotexists nm dbnm */
+ 196, /* (14) createkw ::= CREATE */
+ 198, /* (15) ifnotexists ::= */
+ 198, /* (16) ifnotexists ::= IF NOT EXISTS */
+ 197, /* (17) temp ::= TEMP */
+ 197, /* (18) temp ::= */
+ 195, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_options */
+ 195, /* (20) create_table_args ::= AS select */
+ 202, /* (21) table_options ::= */
+ 202, /* (22) table_options ::= WITHOUT nm */
+ 204, /* (23) columnname ::= nm typetoken */
+ 206, /* (24) typetoken ::= */
+ 206, /* (25) typetoken ::= typename LP signed RP */
+ 206, /* (26) typetoken ::= typename LP signed COMMA signed RP */
+ 207, /* (27) typename ::= typename ID|STRING */
+ 211, /* (28) scanpt ::= */
+ 212, /* (29) scantok ::= */
+ 213, /* (30) ccons ::= CONSTRAINT nm */
+ 213, /* (31) ccons ::= DEFAULT scantok term */
+ 213, /* (32) ccons ::= DEFAULT LP expr RP */
+ 213, /* (33) ccons ::= DEFAULT PLUS scantok term */
+ 213, /* (34) ccons ::= DEFAULT MINUS scantok term */
+ 213, /* (35) ccons ::= DEFAULT scantok ID|INDEXED */
+ 213, /* (36) ccons ::= NOT NULL onconf */
+ 213, /* (37) ccons ::= PRIMARY KEY sortorder onconf autoinc */
+ 213, /* (38) ccons ::= UNIQUE onconf */
+ 213, /* (39) ccons ::= CHECK LP expr RP */
+ 213, /* (40) ccons ::= REFERENCES nm eidlist_opt refargs */
+ 213, /* (41) ccons ::= defer_subclause */
+ 213, /* (42) ccons ::= COLLATE ID|STRING */
+ 222, /* (43) generated ::= LP expr RP */
+ 222, /* (44) generated ::= LP expr RP ID */
+ 218, /* (45) autoinc ::= */
+ 218, /* (46) autoinc ::= AUTOINCR */
+ 220, /* (47) refargs ::= */
+ 220, /* (48) refargs ::= refargs refarg */
+ 223, /* (49) refarg ::= MATCH nm */
+ 223, /* (50) refarg ::= ON INSERT refact */
+ 223, /* (51) refarg ::= ON DELETE refact */
+ 223, /* (52) refarg ::= ON UPDATE refact */
+ 224, /* (53) refact ::= SET NULL */
+ 224, /* (54) refact ::= SET DEFAULT */
+ 224, /* (55) refact ::= CASCADE */
+ 224, /* (56) refact ::= RESTRICT */
+ 224, /* (57) refact ::= NO ACTION */
+ 221, /* (58) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */
+ 221, /* (59) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */
+ 225, /* (60) init_deferred_pred_opt ::= */
+ 225, /* (61) init_deferred_pred_opt ::= INITIALLY DEFERRED */
+ 225, /* (62) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */
+ 201, /* (63) conslist_opt ::= */
+ 227, /* (64) tconscomma ::= COMMA */
+ 228, /* (65) tcons ::= CONSTRAINT nm */
+ 228, /* (66) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */
+ 228, /* (67) tcons ::= UNIQUE LP sortlist RP onconf */
+ 228, /* (68) tcons ::= CHECK LP expr RP onconf */
+ 228, /* (69) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */
+ 231, /* (70) defer_subclause_opt ::= */
+ 216, /* (71) onconf ::= */
+ 216, /* (72) onconf ::= ON CONFLICT resolvetype */
+ 232, /* (73) orconf ::= */
+ 232, /* (74) orconf ::= OR resolvetype */
+ 233, /* (75) resolvetype ::= IGNORE */
+ 233, /* (76) resolvetype ::= REPLACE */
+ 189, /* (77) cmd ::= DROP TABLE ifexists fullname */
+ 235, /* (78) ifexists ::= IF EXISTS */
+ 235, /* (79) ifexists ::= */
+ 189, /* (80) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */
+ 189, /* (81) cmd ::= DROP VIEW ifexists fullname */
+ 189, /* (82) cmd ::= select */
+ 203, /* (83) select ::= WITH wqlist selectnowith */
+ 203, /* (84) select ::= WITH RECURSIVE wqlist selectnowith */
+ 203, /* (85) select ::= selectnowith */
+ 237, /* (86) selectnowith ::= selectnowith multiselect_op oneselect */
+ 240, /* (87) multiselect_op ::= UNION */
+ 240, /* (88) multiselect_op ::= UNION ALL */
+ 240, /* (89) multiselect_op ::= EXCEPT|INTERSECT */
+ 238, /* (90) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */
+ 238, /* (91) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */
+ 250, /* (92) values ::= VALUES LP nexprlist RP */
+ 250, /* (93) values ::= values COMMA LP nexprlist RP */
+ 241, /* (94) distinct ::= DISTINCT */
+ 241, /* (95) distinct ::= ALL */
+ 241, /* (96) distinct ::= */
+ 252, /* (97) sclp ::= */
+ 242, /* (98) selcollist ::= sclp scanpt expr scanpt as */
+ 242, /* (99) selcollist ::= sclp scanpt STAR */
+ 242, /* (100) selcollist ::= sclp scanpt nm DOT STAR */
+ 253, /* (101) as ::= AS nm */
+ 253, /* (102) as ::= */
+ 243, /* (103) from ::= */
+ 243, /* (104) from ::= FROM seltablist */
+ 255, /* (105) stl_prefix ::= seltablist joinop */
+ 255, /* (106) stl_prefix ::= */
+ 254, /* (107) seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */
+ 254, /* (108) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */
+ 254, /* (109) seltablist ::= stl_prefix LP select RP as on_opt using_opt */
+ 254, /* (110) seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */
+ 199, /* (111) dbnm ::= */
+ 199, /* (112) dbnm ::= DOT nm */
+ 236, /* (113) fullname ::= nm */
+ 236, /* (114) fullname ::= nm DOT nm */
+ 261, /* (115) xfullname ::= nm */
+ 261, /* (116) xfullname ::= nm DOT nm */
+ 261, /* (117) xfullname ::= nm DOT nm AS nm */
+ 261, /* (118) xfullname ::= nm AS nm */
+ 256, /* (119) joinop ::= COMMA|JOIN */
+ 256, /* (120) joinop ::= JOIN_KW JOIN */
+ 256, /* (121) joinop ::= JOIN_KW nm JOIN */
+ 256, /* (122) joinop ::= JOIN_KW nm nm JOIN */
+ 258, /* (123) on_opt ::= ON expr */
+ 258, /* (124) on_opt ::= */
+ 257, /* (125) indexed_opt ::= */
+ 257, /* (126) indexed_opt ::= INDEXED BY nm */
+ 257, /* (127) indexed_opt ::= NOT INDEXED */
+ 259, /* (128) using_opt ::= USING LP idlist RP */
+ 259, /* (129) using_opt ::= */
+ 247, /* (130) orderby_opt ::= */
+ 247, /* (131) orderby_opt ::= ORDER BY sortlist */
+ 229, /* (132) sortlist ::= sortlist COMMA expr sortorder nulls */
+ 229, /* (133) sortlist ::= expr sortorder nulls */
+ 217, /* (134) sortorder ::= ASC */
+ 217, /* (135) sortorder ::= DESC */
+ 217, /* (136) sortorder ::= */
+ 263, /* (137) nulls ::= NULLS FIRST */
+ 263, /* (138) nulls ::= NULLS LAST */
+ 263, /* (139) nulls ::= */
+ 245, /* (140) groupby_opt ::= */
+ 245, /* (141) groupby_opt ::= GROUP BY nexprlist */
+ 246, /* (142) having_opt ::= */
+ 246, /* (143) having_opt ::= HAVING expr */
+ 248, /* (144) limit_opt ::= */
+ 248, /* (145) limit_opt ::= LIMIT expr */
+ 248, /* (146) limit_opt ::= LIMIT expr OFFSET expr */
+ 248, /* (147) limit_opt ::= LIMIT expr COMMA expr */
+ 189, /* (148) cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */
+ 244, /* (149) where_opt ::= */
+ 244, /* (150) where_opt ::= WHERE expr */
+ 265, /* (151) where_opt_ret ::= */
+ 265, /* (152) where_opt_ret ::= WHERE expr */
+ 265, /* (153) where_opt_ret ::= RETURNING selcollist */
+ 265, /* (154) where_opt_ret ::= WHERE expr RETURNING selcollist */
+ 189, /* (155) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */
+ 266, /* (156) setlist ::= setlist COMMA nm EQ expr */
+ 266, /* (157) setlist ::= setlist COMMA LP idlist RP EQ expr */
+ 266, /* (158) setlist ::= nm EQ expr */
+ 266, /* (159) setlist ::= LP idlist RP EQ expr */
+ 189, /* (160) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */
+ 189, /* (161) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */
+ 269, /* (162) upsert ::= */
+ 269, /* (163) upsert ::= RETURNING selcollist */
+ 269, /* (164) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */
+ 269, /* (165) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */
+ 269, /* (166) upsert ::= ON CONFLICT DO NOTHING returning */
+ 269, /* (167) upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */
+ 270, /* (168) returning ::= RETURNING selcollist */
+ 267, /* (169) insert_cmd ::= INSERT orconf */
+ 267, /* (170) insert_cmd ::= REPLACE */
+ 268, /* (171) idlist_opt ::= */
+ 268, /* (172) idlist_opt ::= LP idlist RP */
+ 262, /* (173) idlist ::= idlist COMMA nm */
+ 262, /* (174) idlist ::= nm */
+ 215, /* (175) expr ::= LP expr RP */
+ 215, /* (176) expr ::= ID|INDEXED */
+ 215, /* (177) expr ::= JOIN_KW */
+ 215, /* (178) expr ::= nm DOT nm */
+ 215, /* (179) expr ::= nm DOT nm DOT nm */
+ 214, /* (180) term ::= NULL|FLOAT|BLOB */
+ 214, /* (181) term ::= STRING */
+ 214, /* (182) term ::= INTEGER */
+ 215, /* (183) expr ::= VARIABLE */
+ 215, /* (184) expr ::= expr COLLATE ID|STRING */
+ 215, /* (185) expr ::= CAST LP expr AS typetoken RP */
+ 215, /* (186) expr ::= ID|INDEXED LP distinct exprlist RP */
+ 215, /* (187) expr ::= ID|INDEXED LP STAR RP */
+ 215, /* (188) expr ::= ID|INDEXED LP distinct exprlist RP filter_over */
+ 215, /* (189) expr ::= ID|INDEXED LP STAR RP filter_over */
+ 214, /* (190) term ::= CTIME_KW */
+ 215, /* (191) expr ::= LP nexprlist COMMA expr RP */
+ 215, /* (192) expr ::= expr AND expr */
+ 215, /* (193) expr ::= expr OR expr */
+ 215, /* (194) expr ::= expr LT|GT|GE|LE expr */
+ 215, /* (195) expr ::= expr EQ|NE expr */
+ 215, /* (196) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */
+ 215, /* (197) expr ::= expr PLUS|MINUS expr */
+ 215, /* (198) expr ::= expr STAR|SLASH|REM expr */
+ 215, /* (199) expr ::= expr CONCAT expr */
+ 272, /* (200) likeop ::= NOT LIKE_KW|MATCH */
+ 215, /* (201) expr ::= expr likeop expr */
+ 215, /* (202) expr ::= expr likeop expr ESCAPE expr */
+ 215, /* (203) expr ::= expr ISNULL|NOTNULL */
+ 215, /* (204) expr ::= expr NOT NULL */
+ 215, /* (205) expr ::= expr IS expr */
+ 215, /* (206) expr ::= expr IS NOT expr */
+ 215, /* (207) expr ::= NOT expr */
+ 215, /* (208) expr ::= BITNOT expr */
+ 215, /* (209) expr ::= PLUS|MINUS expr */
+ 273, /* (210) between_op ::= BETWEEN */
+ 273, /* (211) between_op ::= NOT BETWEEN */
+ 215, /* (212) expr ::= expr between_op expr AND expr */
+ 274, /* (213) in_op ::= IN */
+ 274, /* (214) in_op ::= NOT IN */
+ 215, /* (215) expr ::= expr in_op LP exprlist RP */
+ 215, /* (216) expr ::= LP select RP */
+ 215, /* (217) expr ::= expr in_op LP select RP */
+ 215, /* (218) expr ::= expr in_op nm dbnm paren_exprlist */
+ 215, /* (219) expr ::= EXISTS LP select RP */
+ 215, /* (220) expr ::= CASE case_operand case_exprlist case_else END */
+ 277, /* (221) case_exprlist ::= case_exprlist WHEN expr THEN expr */
+ 277, /* (222) case_exprlist ::= WHEN expr THEN expr */
+ 278, /* (223) case_else ::= ELSE expr */
+ 278, /* (224) case_else ::= */
+ 276, /* (225) case_operand ::= expr */
+ 276, /* (226) case_operand ::= */
+ 260, /* (227) exprlist ::= */
+ 251, /* (228) nexprlist ::= nexprlist COMMA expr */
+ 251, /* (229) nexprlist ::= expr */
+ 275, /* (230) paren_exprlist ::= */
+ 275, /* (231) paren_exprlist ::= LP exprlist RP */
+ 189, /* (232) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
+ 279, /* (233) uniqueflag ::= UNIQUE */
+ 279, /* (234) uniqueflag ::= */
+ 219, /* (235) eidlist_opt ::= */
+ 219, /* (236) eidlist_opt ::= LP eidlist RP */
+ 230, /* (237) eidlist ::= eidlist COMMA nm collate sortorder */
+ 230, /* (238) eidlist ::= nm collate sortorder */
+ 280, /* (239) collate ::= */
+ 280, /* (240) collate ::= COLLATE ID|STRING */
+ 189, /* (241) cmd ::= DROP INDEX ifexists fullname */
+ 189, /* (242) cmd ::= VACUUM vinto */
+ 189, /* (243) cmd ::= VACUUM nm vinto */
+ 281, /* (244) vinto ::= INTO expr */
+ 281, /* (245) vinto ::= */
+ 189, /* (246) cmd ::= PRAGMA nm dbnm */
+ 189, /* (247) cmd ::= PRAGMA nm dbnm EQ nmnum */
+ 189, /* (248) cmd ::= PRAGMA nm dbnm LP nmnum RP */
+ 189, /* (249) cmd ::= PRAGMA nm dbnm EQ minus_num */
+ 189, /* (250) cmd ::= PRAGMA nm dbnm LP minus_num RP */
+ 209, /* (251) plus_num ::= PLUS INTEGER|FLOAT */
+ 210, /* (252) minus_num ::= MINUS INTEGER|FLOAT */
+ 189, /* (253) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
+ 283, /* (254) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
+ 285, /* (255) trigger_time ::= BEFORE|AFTER */
+ 285, /* (256) trigger_time ::= INSTEAD OF */
+ 285, /* (257) trigger_time ::= */
+ 286, /* (258) trigger_event ::= DELETE|INSERT */
+ 286, /* (259) trigger_event ::= UPDATE */
+ 286, /* (260) trigger_event ::= UPDATE OF idlist */
+ 288, /* (261) when_clause ::= */
+ 288, /* (262) when_clause ::= WHEN expr */
+ 284, /* (263) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
+ 284, /* (264) trigger_cmd_list ::= trigger_cmd SEMI */
+ 290, /* (265) trnm ::= nm DOT nm */
+ 291, /* (266) tridxby ::= INDEXED BY nm */
+ 291, /* (267) tridxby ::= NOT INDEXED */
+ 289, /* (268) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
+ 289, /* (269) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
+ 289, /* (270) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
+ 289, /* (271) trigger_cmd ::= scanpt select scanpt */
+ 215, /* (272) expr ::= RAISE LP IGNORE RP */
+ 215, /* (273) expr ::= RAISE LP raisetype COMMA nm RP */
+ 234, /* (274) raisetype ::= ROLLBACK */
+ 234, /* (275) raisetype ::= ABORT */
+ 234, /* (276) raisetype ::= FAIL */
+ 189, /* (277) cmd ::= DROP TRIGGER ifexists fullname */
+ 189, /* (278) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
+ 189, /* (279) cmd ::= DETACH database_kw_opt expr */
+ 293, /* (280) key_opt ::= */
+ 293, /* (281) key_opt ::= KEY expr */
+ 189, /* (282) cmd ::= REINDEX */
+ 189, /* (283) cmd ::= REINDEX nm dbnm */
+ 189, /* (284) cmd ::= ANALYZE */
+ 189, /* (285) cmd ::= ANALYZE nm dbnm */
+ 189, /* (286) cmd ::= ALTER TABLE fullname RENAME TO nm */
+ 189, /* (287) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
+ 189, /* (288) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */
+ 294, /* (289) add_column_fullname ::= fullname */
+ 189, /* (290) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
+ 189, /* (291) cmd ::= create_vtab */
+ 189, /* (292) cmd ::= create_vtab LP vtabarglist RP */
+ 296, /* (293) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
+ 298, /* (294) vtabarg ::= */
+ 299, /* (295) vtabargtoken ::= ANY */
+ 299, /* (296) vtabargtoken ::= lp anylist RP */
+ 300, /* (297) lp ::= LP */
+ 264, /* (298) with ::= WITH wqlist */
+ 264, /* (299) with ::= WITH RECURSIVE wqlist */
+ 303, /* (300) wqas ::= AS */
+ 303, /* (301) wqas ::= AS MATERIALIZED */
+ 303, /* (302) wqas ::= AS NOT MATERIALIZED */
+ 302, /* (303) wqitem ::= nm eidlist_opt wqas LP select RP */
+ 239, /* (304) wqlist ::= wqitem */
+ 239, /* (305) wqlist ::= wqlist COMMA wqitem */
+ 304, /* (306) windowdefn_list ::= windowdefn */
+ 304, /* (307) windowdefn_list ::= windowdefn_list COMMA windowdefn */
+ 305, /* (308) windowdefn ::= nm AS LP window RP */
+ 306, /* (309) window ::= PARTITION BY nexprlist orderby_opt frame_opt */
+ 306, /* (310) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
+ 306, /* (311) window ::= ORDER BY sortlist frame_opt */
+ 306, /* (312) window ::= nm ORDER BY sortlist frame_opt */
+ 306, /* (313) window ::= frame_opt */
+ 306, /* (314) window ::= nm frame_opt */
+ 307, /* (315) frame_opt ::= */
+ 307, /* (316) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
+ 307, /* (317) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
+ 311, /* (318) range_or_rows ::= RANGE|ROWS|GROUPS */
+ 313, /* (319) frame_bound_s ::= frame_bound */
+ 313, /* (320) frame_bound_s ::= UNBOUNDED PRECEDING */
+ 314, /* (321) frame_bound_e ::= frame_bound */
+ 314, /* (322) frame_bound_e ::= UNBOUNDED FOLLOWING */
+ 312, /* (323) frame_bound ::= expr PRECEDING|FOLLOWING */
+ 312, /* (324) frame_bound ::= CURRENT ROW */
+ 315, /* (325) frame_exclude_opt ::= */
+ 315, /* (326) frame_exclude_opt ::= EXCLUDE frame_exclude */
+ 316, /* (327) frame_exclude ::= NO OTHERS */
+ 316, /* (328) frame_exclude ::= CURRENT ROW */
+ 316, /* (329) frame_exclude ::= GROUP|TIES */
+ 249, /* (330) window_clause ::= WINDOW windowdefn_list */
+ 271, /* (331) filter_over ::= filter_clause over_clause */
+ 271, /* (332) filter_over ::= over_clause */
+ 271, /* (333) filter_over ::= filter_clause */
+ 310, /* (334) over_clause ::= OVER LP window RP */
+ 310, /* (335) over_clause ::= OVER nm */
+ 309, /* (336) filter_clause ::= FILTER LP WHERE expr RP */
+ 184, /* (337) input ::= cmdlist */
+ 185, /* (338) cmdlist ::= cmdlist ecmd */
+ 185, /* (339) cmdlist ::= ecmd */
+ 186, /* (340) ecmd ::= SEMI */
+ 186, /* (341) ecmd ::= cmdx SEMI */
+ 186, /* (342) ecmd ::= explain cmdx SEMI */
+ 191, /* (343) trans_opt ::= */
+ 191, /* (344) trans_opt ::= TRANSACTION */
+ 191, /* (345) trans_opt ::= TRANSACTION nm */
+ 193, /* (346) savepoint_opt ::= SAVEPOINT */
+ 193, /* (347) savepoint_opt ::= */
+ 189, /* (348) cmd ::= create_table create_table_args */
+ 200, /* (349) columnlist ::= columnlist COMMA columnname carglist */
+ 200, /* (350) columnlist ::= columnname carglist */
+ 192, /* (351) nm ::= ID|INDEXED */
+ 192, /* (352) nm ::= STRING */
+ 192, /* (353) nm ::= JOIN_KW */
+ 206, /* (354) typetoken ::= typename */
+ 207, /* (355) typename ::= ID|STRING */
+ 208, /* (356) signed ::= plus_num */
+ 208, /* (357) signed ::= minus_num */
+ 205, /* (358) carglist ::= carglist ccons */
+ 205, /* (359) carglist ::= */
+ 213, /* (360) ccons ::= NULL onconf */
+ 213, /* (361) ccons ::= GENERATED ALWAYS AS generated */
+ 213, /* (362) ccons ::= AS generated */
+ 201, /* (363) conslist_opt ::= COMMA conslist */
+ 226, /* (364) conslist ::= conslist tconscomma tcons */
+ 226, /* (365) conslist ::= tcons */
+ 227, /* (366) tconscomma ::= */
+ 231, /* (367) defer_subclause_opt ::= defer_subclause */
+ 233, /* (368) resolvetype ::= raisetype */
+ 237, /* (369) selectnowith ::= oneselect */
+ 238, /* (370) oneselect ::= values */
+ 252, /* (371) sclp ::= selcollist COMMA */
+ 253, /* (372) as ::= ID|STRING */
+ 270, /* (373) returning ::= */
+ 215, /* (374) expr ::= term */
+ 272, /* (375) likeop ::= LIKE_KW|MATCH */
+ 260, /* (376) exprlist ::= nexprlist */
+ 282, /* (377) nmnum ::= plus_num */
+ 282, /* (378) nmnum ::= nm */
+ 282, /* (379) nmnum ::= ON */
+ 282, /* (380) nmnum ::= DELETE */
+ 282, /* (381) nmnum ::= DEFAULT */
+ 209, /* (382) plus_num ::= INTEGER|FLOAT */
+ 287, /* (383) foreach_clause ::= */
+ 287, /* (384) foreach_clause ::= FOR EACH ROW */
+ 290, /* (385) trnm ::= nm */
+ 291, /* (386) tridxby ::= */
+ 292, /* (387) database_kw_opt ::= DATABASE */
+ 292, /* (388) database_kw_opt ::= */
+ 295, /* (389) kwcolumn_opt ::= */
+ 295, /* (390) kwcolumn_opt ::= COLUMNKW */
+ 297, /* (391) vtabarglist ::= vtabarg */
+ 297, /* (392) vtabarglist ::= vtabarglist COMMA vtabarg */
+ 298, /* (393) vtabarg ::= vtabarg vtabargtoken */
+ 301, /* (394) anylist ::= */
+ 301, /* (395) anylist ::= anylist LP anylist RP */
+ 301, /* (396) anylist ::= anylist ANY */
+ 264, /* (397) with ::= */
};
/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number
@@ -157209,243 +161251,256 @@ static const signed char yyRuleInfoNRhs[] = {
-2, /* (145) limit_opt ::= LIMIT expr */
-4, /* (146) limit_opt ::= LIMIT expr OFFSET expr */
-4, /* (147) limit_opt ::= LIMIT expr COMMA expr */
- -6, /* (148) cmd ::= with DELETE FROM xfullname indexed_opt where_opt */
+ -6, /* (148) cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */
0, /* (149) where_opt ::= */
-2, /* (150) where_opt ::= WHERE expr */
- -9, /* (151) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt */
- -5, /* (152) setlist ::= setlist COMMA nm EQ expr */
- -7, /* (153) setlist ::= setlist COMMA LP idlist RP EQ expr */
- -3, /* (154) setlist ::= nm EQ expr */
- -5, /* (155) setlist ::= LP idlist RP EQ expr */
- -7, /* (156) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */
- -7, /* (157) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES */
- 0, /* (158) upsert ::= */
- -11, /* (159) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt */
- -8, /* (160) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING */
- -4, /* (161) upsert ::= ON CONFLICT DO NOTHING */
- -2, /* (162) insert_cmd ::= INSERT orconf */
- -1, /* (163) insert_cmd ::= REPLACE */
- 0, /* (164) idlist_opt ::= */
- -3, /* (165) idlist_opt ::= LP idlist RP */
- -3, /* (166) idlist ::= idlist COMMA nm */
- -1, /* (167) idlist ::= nm */
- -3, /* (168) expr ::= LP expr RP */
- -1, /* (169) expr ::= ID|INDEXED */
- -1, /* (170) expr ::= JOIN_KW */
- -3, /* (171) expr ::= nm DOT nm */
- -5, /* (172) expr ::= nm DOT nm DOT nm */
- -1, /* (173) term ::= NULL|FLOAT|BLOB */
- -1, /* (174) term ::= STRING */
- -1, /* (175) term ::= INTEGER */
- -1, /* (176) expr ::= VARIABLE */
- -3, /* (177) expr ::= expr COLLATE ID|STRING */
- -6, /* (178) expr ::= CAST LP expr AS typetoken RP */
- -5, /* (179) expr ::= ID|INDEXED LP distinct exprlist RP */
- -4, /* (180) expr ::= ID|INDEXED LP STAR RP */
- -6, /* (181) expr ::= ID|INDEXED LP distinct exprlist RP filter_over */
- -5, /* (182) expr ::= ID|INDEXED LP STAR RP filter_over */
- -1, /* (183) term ::= CTIME_KW */
- -5, /* (184) expr ::= LP nexprlist COMMA expr RP */
- -3, /* (185) expr ::= expr AND expr */
- -3, /* (186) expr ::= expr OR expr */
- -3, /* (187) expr ::= expr LT|GT|GE|LE expr */
- -3, /* (188) expr ::= expr EQ|NE expr */
- -3, /* (189) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */
- -3, /* (190) expr ::= expr PLUS|MINUS expr */
- -3, /* (191) expr ::= expr STAR|SLASH|REM expr */
- -3, /* (192) expr ::= expr CONCAT expr */
- -2, /* (193) likeop ::= NOT LIKE_KW|MATCH */
- -3, /* (194) expr ::= expr likeop expr */
- -5, /* (195) expr ::= expr likeop expr ESCAPE expr */
- -2, /* (196) expr ::= expr ISNULL|NOTNULL */
- -3, /* (197) expr ::= expr NOT NULL */
- -3, /* (198) expr ::= expr IS expr */
- -4, /* (199) expr ::= expr IS NOT expr */
- -2, /* (200) expr ::= NOT expr */
- -2, /* (201) expr ::= BITNOT expr */
- -2, /* (202) expr ::= PLUS|MINUS expr */
- -1, /* (203) between_op ::= BETWEEN */
- -2, /* (204) between_op ::= NOT BETWEEN */
- -5, /* (205) expr ::= expr between_op expr AND expr */
- -1, /* (206) in_op ::= IN */
- -2, /* (207) in_op ::= NOT IN */
- -5, /* (208) expr ::= expr in_op LP exprlist RP */
- -3, /* (209) expr ::= LP select RP */
- -5, /* (210) expr ::= expr in_op LP select RP */
- -5, /* (211) expr ::= expr in_op nm dbnm paren_exprlist */
- -4, /* (212) expr ::= EXISTS LP select RP */
- -5, /* (213) expr ::= CASE case_operand case_exprlist case_else END */
- -5, /* (214) case_exprlist ::= case_exprlist WHEN expr THEN expr */
- -4, /* (215) case_exprlist ::= WHEN expr THEN expr */
- -2, /* (216) case_else ::= ELSE expr */
- 0, /* (217) case_else ::= */
- -1, /* (218) case_operand ::= expr */
- 0, /* (219) case_operand ::= */
- 0, /* (220) exprlist ::= */
- -3, /* (221) nexprlist ::= nexprlist COMMA expr */
- -1, /* (222) nexprlist ::= expr */
- 0, /* (223) paren_exprlist ::= */
- -3, /* (224) paren_exprlist ::= LP exprlist RP */
- -12, /* (225) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
- -1, /* (226) uniqueflag ::= UNIQUE */
- 0, /* (227) uniqueflag ::= */
- 0, /* (228) eidlist_opt ::= */
- -3, /* (229) eidlist_opt ::= LP eidlist RP */
- -5, /* (230) eidlist ::= eidlist COMMA nm collate sortorder */
- -3, /* (231) eidlist ::= nm collate sortorder */
- 0, /* (232) collate ::= */
- -2, /* (233) collate ::= COLLATE ID|STRING */
- -4, /* (234) cmd ::= DROP INDEX ifexists fullname */
- -2, /* (235) cmd ::= VACUUM vinto */
- -3, /* (236) cmd ::= VACUUM nm vinto */
- -2, /* (237) vinto ::= INTO expr */
- 0, /* (238) vinto ::= */
- -3, /* (239) cmd ::= PRAGMA nm dbnm */
- -5, /* (240) cmd ::= PRAGMA nm dbnm EQ nmnum */
- -6, /* (241) cmd ::= PRAGMA nm dbnm LP nmnum RP */
- -5, /* (242) cmd ::= PRAGMA nm dbnm EQ minus_num */
- -6, /* (243) cmd ::= PRAGMA nm dbnm LP minus_num RP */
- -2, /* (244) plus_num ::= PLUS INTEGER|FLOAT */
- -2, /* (245) minus_num ::= MINUS INTEGER|FLOAT */
- -5, /* (246) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
- -11, /* (247) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
- -1, /* (248) trigger_time ::= BEFORE|AFTER */
- -2, /* (249) trigger_time ::= INSTEAD OF */
- 0, /* (250) trigger_time ::= */
- -1, /* (251) trigger_event ::= DELETE|INSERT */
- -1, /* (252) trigger_event ::= UPDATE */
- -3, /* (253) trigger_event ::= UPDATE OF idlist */
- 0, /* (254) when_clause ::= */
- -2, /* (255) when_clause ::= WHEN expr */
- -3, /* (256) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
- -2, /* (257) trigger_cmd_list ::= trigger_cmd SEMI */
- -3, /* (258) trnm ::= nm DOT nm */
- -3, /* (259) tridxby ::= INDEXED BY nm */
- -2, /* (260) tridxby ::= NOT INDEXED */
- -9, /* (261) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
- -8, /* (262) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
- -6, /* (263) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
- -3, /* (264) trigger_cmd ::= scanpt select scanpt */
- -4, /* (265) expr ::= RAISE LP IGNORE RP */
- -6, /* (266) expr ::= RAISE LP raisetype COMMA nm RP */
- -1, /* (267) raisetype ::= ROLLBACK */
- -1, /* (268) raisetype ::= ABORT */
- -1, /* (269) raisetype ::= FAIL */
- -4, /* (270) cmd ::= DROP TRIGGER ifexists fullname */
- -6, /* (271) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
- -3, /* (272) cmd ::= DETACH database_kw_opt expr */
- 0, /* (273) key_opt ::= */
- -2, /* (274) key_opt ::= KEY expr */
- -1, /* (275) cmd ::= REINDEX */
- -3, /* (276) cmd ::= REINDEX nm dbnm */
- -1, /* (277) cmd ::= ANALYZE */
- -3, /* (278) cmd ::= ANALYZE nm dbnm */
- -6, /* (279) cmd ::= ALTER TABLE fullname RENAME TO nm */
- -7, /* (280) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
- -1, /* (281) add_column_fullname ::= fullname */
- -8, /* (282) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
- -1, /* (283) cmd ::= create_vtab */
- -4, /* (284) cmd ::= create_vtab LP vtabarglist RP */
- -8, /* (285) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
- 0, /* (286) vtabarg ::= */
- -1, /* (287) vtabargtoken ::= ANY */
- -3, /* (288) vtabargtoken ::= lp anylist RP */
- -1, /* (289) lp ::= LP */
- -2, /* (290) with ::= WITH wqlist */
- -3, /* (291) with ::= WITH RECURSIVE wqlist */
- -6, /* (292) wqlist ::= nm eidlist_opt AS LP select RP */
- -8, /* (293) wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */
- -1, /* (294) windowdefn_list ::= windowdefn */
- -3, /* (295) windowdefn_list ::= windowdefn_list COMMA windowdefn */
- -5, /* (296) windowdefn ::= nm AS LP window RP */
- -5, /* (297) window ::= PARTITION BY nexprlist orderby_opt frame_opt */
- -6, /* (298) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
- -4, /* (299) window ::= ORDER BY sortlist frame_opt */
- -5, /* (300) window ::= nm ORDER BY sortlist frame_opt */
- -1, /* (301) window ::= frame_opt */
- -2, /* (302) window ::= nm frame_opt */
- 0, /* (303) frame_opt ::= */
- -3, /* (304) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
- -6, /* (305) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
- -1, /* (306) range_or_rows ::= RANGE|ROWS|GROUPS */
- -1, /* (307) frame_bound_s ::= frame_bound */
- -2, /* (308) frame_bound_s ::= UNBOUNDED PRECEDING */
- -1, /* (309) frame_bound_e ::= frame_bound */
- -2, /* (310) frame_bound_e ::= UNBOUNDED FOLLOWING */
- -2, /* (311) frame_bound ::= expr PRECEDING|FOLLOWING */
- -2, /* (312) frame_bound ::= CURRENT ROW */
- 0, /* (313) frame_exclude_opt ::= */
- -2, /* (314) frame_exclude_opt ::= EXCLUDE frame_exclude */
- -2, /* (315) frame_exclude ::= NO OTHERS */
- -2, /* (316) frame_exclude ::= CURRENT ROW */
- -1, /* (317) frame_exclude ::= GROUP|TIES */
- -2, /* (318) window_clause ::= WINDOW windowdefn_list */
- -2, /* (319) filter_over ::= filter_clause over_clause */
- -1, /* (320) filter_over ::= over_clause */
- -1, /* (321) filter_over ::= filter_clause */
- -4, /* (322) over_clause ::= OVER LP window RP */
- -2, /* (323) over_clause ::= OVER nm */
- -5, /* (324) filter_clause ::= FILTER LP WHERE expr RP */
- -1, /* (325) input ::= cmdlist */
- -2, /* (326) cmdlist ::= cmdlist ecmd */
- -1, /* (327) cmdlist ::= ecmd */
- -1, /* (328) ecmd ::= SEMI */
- -2, /* (329) ecmd ::= cmdx SEMI */
- -3, /* (330) ecmd ::= explain cmdx SEMI */
- 0, /* (331) trans_opt ::= */
- -1, /* (332) trans_opt ::= TRANSACTION */
- -2, /* (333) trans_opt ::= TRANSACTION nm */
- -1, /* (334) savepoint_opt ::= SAVEPOINT */
- 0, /* (335) savepoint_opt ::= */
- -2, /* (336) cmd ::= create_table create_table_args */
- -4, /* (337) columnlist ::= columnlist COMMA columnname carglist */
- -2, /* (338) columnlist ::= columnname carglist */
- -1, /* (339) nm ::= ID|INDEXED */
- -1, /* (340) nm ::= STRING */
- -1, /* (341) nm ::= JOIN_KW */
- -1, /* (342) typetoken ::= typename */
- -1, /* (343) typename ::= ID|STRING */
- -1, /* (344) signed ::= plus_num */
- -1, /* (345) signed ::= minus_num */
- -2, /* (346) carglist ::= carglist ccons */
- 0, /* (347) carglist ::= */
- -2, /* (348) ccons ::= NULL onconf */
- -4, /* (349) ccons ::= GENERATED ALWAYS AS generated */
- -2, /* (350) ccons ::= AS generated */
- -2, /* (351) conslist_opt ::= COMMA conslist */
- -3, /* (352) conslist ::= conslist tconscomma tcons */
- -1, /* (353) conslist ::= tcons */
- 0, /* (354) tconscomma ::= */
- -1, /* (355) defer_subclause_opt ::= defer_subclause */
- -1, /* (356) resolvetype ::= raisetype */
- -1, /* (357) selectnowith ::= oneselect */
- -1, /* (358) oneselect ::= values */
- -2, /* (359) sclp ::= selcollist COMMA */
- -1, /* (360) as ::= ID|STRING */
- -1, /* (361) expr ::= term */
- -1, /* (362) likeop ::= LIKE_KW|MATCH */
- -1, /* (363) exprlist ::= nexprlist */
- -1, /* (364) nmnum ::= plus_num */
- -1, /* (365) nmnum ::= nm */
- -1, /* (366) nmnum ::= ON */
- -1, /* (367) nmnum ::= DELETE */
- -1, /* (368) nmnum ::= DEFAULT */
- -1, /* (369) plus_num ::= INTEGER|FLOAT */
- 0, /* (370) foreach_clause ::= */
- -3, /* (371) foreach_clause ::= FOR EACH ROW */
- -1, /* (372) trnm ::= nm */
- 0, /* (373) tridxby ::= */
- -1, /* (374) database_kw_opt ::= DATABASE */
- 0, /* (375) database_kw_opt ::= */
- 0, /* (376) kwcolumn_opt ::= */
- -1, /* (377) kwcolumn_opt ::= COLUMNKW */
- -1, /* (378) vtabarglist ::= vtabarg */
- -3, /* (379) vtabarglist ::= vtabarglist COMMA vtabarg */
- -2, /* (380) vtabarg ::= vtabarg vtabargtoken */
- 0, /* (381) anylist ::= */
- -4, /* (382) anylist ::= anylist LP anylist RP */
- -2, /* (383) anylist ::= anylist ANY */
- 0, /* (384) with ::= */
+ 0, /* (151) where_opt_ret ::= */
+ -2, /* (152) where_opt_ret ::= WHERE expr */
+ -2, /* (153) where_opt_ret ::= RETURNING selcollist */
+ -4, /* (154) where_opt_ret ::= WHERE expr RETURNING selcollist */
+ -9, /* (155) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */
+ -5, /* (156) setlist ::= setlist COMMA nm EQ expr */
+ -7, /* (157) setlist ::= setlist COMMA LP idlist RP EQ expr */
+ -3, /* (158) setlist ::= nm EQ expr */
+ -5, /* (159) setlist ::= LP idlist RP EQ expr */
+ -7, /* (160) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */
+ -8, /* (161) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */
+ 0, /* (162) upsert ::= */
+ -2, /* (163) upsert ::= RETURNING selcollist */
+ -12, /* (164) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */
+ -9, /* (165) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */
+ -5, /* (166) upsert ::= ON CONFLICT DO NOTHING returning */
+ -8, /* (167) upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */
+ -2, /* (168) returning ::= RETURNING selcollist */
+ -2, /* (169) insert_cmd ::= INSERT orconf */
+ -1, /* (170) insert_cmd ::= REPLACE */
+ 0, /* (171) idlist_opt ::= */
+ -3, /* (172) idlist_opt ::= LP idlist RP */
+ -3, /* (173) idlist ::= idlist COMMA nm */
+ -1, /* (174) idlist ::= nm */
+ -3, /* (175) expr ::= LP expr RP */
+ -1, /* (176) expr ::= ID|INDEXED */
+ -1, /* (177) expr ::= JOIN_KW */
+ -3, /* (178) expr ::= nm DOT nm */
+ -5, /* (179) expr ::= nm DOT nm DOT nm */
+ -1, /* (180) term ::= NULL|FLOAT|BLOB */
+ -1, /* (181) term ::= STRING */
+ -1, /* (182) term ::= INTEGER */
+ -1, /* (183) expr ::= VARIABLE */
+ -3, /* (184) expr ::= expr COLLATE ID|STRING */
+ -6, /* (185) expr ::= CAST LP expr AS typetoken RP */
+ -5, /* (186) expr ::= ID|INDEXED LP distinct exprlist RP */
+ -4, /* (187) expr ::= ID|INDEXED LP STAR RP */
+ -6, /* (188) expr ::= ID|INDEXED LP distinct exprlist RP filter_over */
+ -5, /* (189) expr ::= ID|INDEXED LP STAR RP filter_over */
+ -1, /* (190) term ::= CTIME_KW */
+ -5, /* (191) expr ::= LP nexprlist COMMA expr RP */
+ -3, /* (192) expr ::= expr AND expr */
+ -3, /* (193) expr ::= expr OR expr */
+ -3, /* (194) expr ::= expr LT|GT|GE|LE expr */
+ -3, /* (195) expr ::= expr EQ|NE expr */
+ -3, /* (196) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */
+ -3, /* (197) expr ::= expr PLUS|MINUS expr */
+ -3, /* (198) expr ::= expr STAR|SLASH|REM expr */
+ -3, /* (199) expr ::= expr CONCAT expr */
+ -2, /* (200) likeop ::= NOT LIKE_KW|MATCH */
+ -3, /* (201) expr ::= expr likeop expr */
+ -5, /* (202) expr ::= expr likeop expr ESCAPE expr */
+ -2, /* (203) expr ::= expr ISNULL|NOTNULL */
+ -3, /* (204) expr ::= expr NOT NULL */
+ -3, /* (205) expr ::= expr IS expr */
+ -4, /* (206) expr ::= expr IS NOT expr */
+ -2, /* (207) expr ::= NOT expr */
+ -2, /* (208) expr ::= BITNOT expr */
+ -2, /* (209) expr ::= PLUS|MINUS expr */
+ -1, /* (210) between_op ::= BETWEEN */
+ -2, /* (211) between_op ::= NOT BETWEEN */
+ -5, /* (212) expr ::= expr between_op expr AND expr */
+ -1, /* (213) in_op ::= IN */
+ -2, /* (214) in_op ::= NOT IN */
+ -5, /* (215) expr ::= expr in_op LP exprlist RP */
+ -3, /* (216) expr ::= LP select RP */
+ -5, /* (217) expr ::= expr in_op LP select RP */
+ -5, /* (218) expr ::= expr in_op nm dbnm paren_exprlist */
+ -4, /* (219) expr ::= EXISTS LP select RP */
+ -5, /* (220) expr ::= CASE case_operand case_exprlist case_else END */
+ -5, /* (221) case_exprlist ::= case_exprlist WHEN expr THEN expr */
+ -4, /* (222) case_exprlist ::= WHEN expr THEN expr */
+ -2, /* (223) case_else ::= ELSE expr */
+ 0, /* (224) case_else ::= */
+ -1, /* (225) case_operand ::= expr */
+ 0, /* (226) case_operand ::= */
+ 0, /* (227) exprlist ::= */
+ -3, /* (228) nexprlist ::= nexprlist COMMA expr */
+ -1, /* (229) nexprlist ::= expr */
+ 0, /* (230) paren_exprlist ::= */
+ -3, /* (231) paren_exprlist ::= LP exprlist RP */
+ -12, /* (232) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
+ -1, /* (233) uniqueflag ::= UNIQUE */
+ 0, /* (234) uniqueflag ::= */
+ 0, /* (235) eidlist_opt ::= */
+ -3, /* (236) eidlist_opt ::= LP eidlist RP */
+ -5, /* (237) eidlist ::= eidlist COMMA nm collate sortorder */
+ -3, /* (238) eidlist ::= nm collate sortorder */
+ 0, /* (239) collate ::= */
+ -2, /* (240) collate ::= COLLATE ID|STRING */
+ -4, /* (241) cmd ::= DROP INDEX ifexists fullname */
+ -2, /* (242) cmd ::= VACUUM vinto */
+ -3, /* (243) cmd ::= VACUUM nm vinto */
+ -2, /* (244) vinto ::= INTO expr */
+ 0, /* (245) vinto ::= */
+ -3, /* (246) cmd ::= PRAGMA nm dbnm */
+ -5, /* (247) cmd ::= PRAGMA nm dbnm EQ nmnum */
+ -6, /* (248) cmd ::= PRAGMA nm dbnm LP nmnum RP */
+ -5, /* (249) cmd ::= PRAGMA nm dbnm EQ minus_num */
+ -6, /* (250) cmd ::= PRAGMA nm dbnm LP minus_num RP */
+ -2, /* (251) plus_num ::= PLUS INTEGER|FLOAT */
+ -2, /* (252) minus_num ::= MINUS INTEGER|FLOAT */
+ -5, /* (253) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
+ -11, /* (254) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
+ -1, /* (255) trigger_time ::= BEFORE|AFTER */
+ -2, /* (256) trigger_time ::= INSTEAD OF */
+ 0, /* (257) trigger_time ::= */
+ -1, /* (258) trigger_event ::= DELETE|INSERT */
+ -1, /* (259) trigger_event ::= UPDATE */
+ -3, /* (260) trigger_event ::= UPDATE OF idlist */
+ 0, /* (261) when_clause ::= */
+ -2, /* (262) when_clause ::= WHEN expr */
+ -3, /* (263) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
+ -2, /* (264) trigger_cmd_list ::= trigger_cmd SEMI */
+ -3, /* (265) trnm ::= nm DOT nm */
+ -3, /* (266) tridxby ::= INDEXED BY nm */
+ -2, /* (267) tridxby ::= NOT INDEXED */
+ -9, /* (268) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
+ -8, /* (269) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
+ -6, /* (270) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
+ -3, /* (271) trigger_cmd ::= scanpt select scanpt */
+ -4, /* (272) expr ::= RAISE LP IGNORE RP */
+ -6, /* (273) expr ::= RAISE LP raisetype COMMA nm RP */
+ -1, /* (274) raisetype ::= ROLLBACK */
+ -1, /* (275) raisetype ::= ABORT */
+ -1, /* (276) raisetype ::= FAIL */
+ -4, /* (277) cmd ::= DROP TRIGGER ifexists fullname */
+ -6, /* (278) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
+ -3, /* (279) cmd ::= DETACH database_kw_opt expr */
+ 0, /* (280) key_opt ::= */
+ -2, /* (281) key_opt ::= KEY expr */
+ -1, /* (282) cmd ::= REINDEX */
+ -3, /* (283) cmd ::= REINDEX nm dbnm */
+ -1, /* (284) cmd ::= ANALYZE */
+ -3, /* (285) cmd ::= ANALYZE nm dbnm */
+ -6, /* (286) cmd ::= ALTER TABLE fullname RENAME TO nm */
+ -7, /* (287) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
+ -6, /* (288) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */
+ -1, /* (289) add_column_fullname ::= fullname */
+ -8, /* (290) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
+ -1, /* (291) cmd ::= create_vtab */
+ -4, /* (292) cmd ::= create_vtab LP vtabarglist RP */
+ -8, /* (293) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
+ 0, /* (294) vtabarg ::= */
+ -1, /* (295) vtabargtoken ::= ANY */
+ -3, /* (296) vtabargtoken ::= lp anylist RP */
+ -1, /* (297) lp ::= LP */
+ -2, /* (298) with ::= WITH wqlist */
+ -3, /* (299) with ::= WITH RECURSIVE wqlist */
+ -1, /* (300) wqas ::= AS */
+ -2, /* (301) wqas ::= AS MATERIALIZED */
+ -3, /* (302) wqas ::= AS NOT MATERIALIZED */
+ -6, /* (303) wqitem ::= nm eidlist_opt wqas LP select RP */
+ -1, /* (304) wqlist ::= wqitem */
+ -3, /* (305) wqlist ::= wqlist COMMA wqitem */
+ -1, /* (306) windowdefn_list ::= windowdefn */
+ -3, /* (307) windowdefn_list ::= windowdefn_list COMMA windowdefn */
+ -5, /* (308) windowdefn ::= nm AS LP window RP */
+ -5, /* (309) window ::= PARTITION BY nexprlist orderby_opt frame_opt */
+ -6, /* (310) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
+ -4, /* (311) window ::= ORDER BY sortlist frame_opt */
+ -5, /* (312) window ::= nm ORDER BY sortlist frame_opt */
+ -1, /* (313) window ::= frame_opt */
+ -2, /* (314) window ::= nm frame_opt */
+ 0, /* (315) frame_opt ::= */
+ -3, /* (316) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
+ -6, /* (317) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
+ -1, /* (318) range_or_rows ::= RANGE|ROWS|GROUPS */
+ -1, /* (319) frame_bound_s ::= frame_bound */
+ -2, /* (320) frame_bound_s ::= UNBOUNDED PRECEDING */
+ -1, /* (321) frame_bound_e ::= frame_bound */
+ -2, /* (322) frame_bound_e ::= UNBOUNDED FOLLOWING */
+ -2, /* (323) frame_bound ::= expr PRECEDING|FOLLOWING */
+ -2, /* (324) frame_bound ::= CURRENT ROW */
+ 0, /* (325) frame_exclude_opt ::= */
+ -2, /* (326) frame_exclude_opt ::= EXCLUDE frame_exclude */
+ -2, /* (327) frame_exclude ::= NO OTHERS */
+ -2, /* (328) frame_exclude ::= CURRENT ROW */
+ -1, /* (329) frame_exclude ::= GROUP|TIES */
+ -2, /* (330) window_clause ::= WINDOW windowdefn_list */
+ -2, /* (331) filter_over ::= filter_clause over_clause */
+ -1, /* (332) filter_over ::= over_clause */
+ -1, /* (333) filter_over ::= filter_clause */
+ -4, /* (334) over_clause ::= OVER LP window RP */
+ -2, /* (335) over_clause ::= OVER nm */
+ -5, /* (336) filter_clause ::= FILTER LP WHERE expr RP */
+ -1, /* (337) input ::= cmdlist */
+ -2, /* (338) cmdlist ::= cmdlist ecmd */
+ -1, /* (339) cmdlist ::= ecmd */
+ -1, /* (340) ecmd ::= SEMI */
+ -2, /* (341) ecmd ::= cmdx SEMI */
+ -3, /* (342) ecmd ::= explain cmdx SEMI */
+ 0, /* (343) trans_opt ::= */
+ -1, /* (344) trans_opt ::= TRANSACTION */
+ -2, /* (345) trans_opt ::= TRANSACTION nm */
+ -1, /* (346) savepoint_opt ::= SAVEPOINT */
+ 0, /* (347) savepoint_opt ::= */
+ -2, /* (348) cmd ::= create_table create_table_args */
+ -4, /* (349) columnlist ::= columnlist COMMA columnname carglist */
+ -2, /* (350) columnlist ::= columnname carglist */
+ -1, /* (351) nm ::= ID|INDEXED */
+ -1, /* (352) nm ::= STRING */
+ -1, /* (353) nm ::= JOIN_KW */
+ -1, /* (354) typetoken ::= typename */
+ -1, /* (355) typename ::= ID|STRING */
+ -1, /* (356) signed ::= plus_num */
+ -1, /* (357) signed ::= minus_num */
+ -2, /* (358) carglist ::= carglist ccons */
+ 0, /* (359) carglist ::= */
+ -2, /* (360) ccons ::= NULL onconf */
+ -4, /* (361) ccons ::= GENERATED ALWAYS AS generated */
+ -2, /* (362) ccons ::= AS generated */
+ -2, /* (363) conslist_opt ::= COMMA conslist */
+ -3, /* (364) conslist ::= conslist tconscomma tcons */
+ -1, /* (365) conslist ::= tcons */
+ 0, /* (366) tconscomma ::= */
+ -1, /* (367) defer_subclause_opt ::= defer_subclause */
+ -1, /* (368) resolvetype ::= raisetype */
+ -1, /* (369) selectnowith ::= oneselect */
+ -1, /* (370) oneselect ::= values */
+ -2, /* (371) sclp ::= selcollist COMMA */
+ -1, /* (372) as ::= ID|STRING */
+ 0, /* (373) returning ::= */
+ -1, /* (374) expr ::= term */
+ -1, /* (375) likeop ::= LIKE_KW|MATCH */
+ -1, /* (376) exprlist ::= nexprlist */
+ -1, /* (377) nmnum ::= plus_num */
+ -1, /* (378) nmnum ::= nm */
+ -1, /* (379) nmnum ::= ON */
+ -1, /* (380) nmnum ::= DELETE */
+ -1, /* (381) nmnum ::= DEFAULT */
+ -1, /* (382) plus_num ::= INTEGER|FLOAT */
+ 0, /* (383) foreach_clause ::= */
+ -3, /* (384) foreach_clause ::= FOR EACH ROW */
+ -1, /* (385) trnm ::= nm */
+ 0, /* (386) tridxby ::= */
+ -1, /* (387) database_kw_opt ::= DATABASE */
+ 0, /* (388) database_kw_opt ::= */
+ 0, /* (389) kwcolumn_opt ::= */
+ -1, /* (390) kwcolumn_opt ::= COLUMNKW */
+ -1, /* (391) vtabarglist ::= vtabarg */
+ -3, /* (392) vtabarglist ::= vtabarglist COMMA vtabarg */
+ -2, /* (393) vtabarg ::= vtabarg vtabargtoken */
+ 0, /* (394) anylist ::= */
+ -4, /* (395) anylist ::= anylist LP anylist RP */
+ -2, /* (396) anylist ::= anylist ANY */
+ 0, /* (397) with ::= */
};
static void yy_accept(yyParser*); /* Forward Declaration */
@@ -157475,54 +161530,6 @@ static YYACTIONTYPE yy_reduce(
(void)yyLookahead;
(void)yyLookaheadToken;
yymsp = yypParser->yytos;
-#ifndef NDEBUG
- if( yyTraceFILE && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){
- yysize = yyRuleInfoNRhs[yyruleno];
- if( yysize ){
- fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n",
- yyTracePrompt,
- yyruleno, yyRuleName[yyruleno],
- yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action",
- yymsp[yysize].stateno);
- }else{
- fprintf(yyTraceFILE, "%sReduce %d [%s]%s.\n",
- yyTracePrompt, yyruleno, yyRuleName[yyruleno],
- yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action");
- }
- }
-#endif /* NDEBUG */
-
- /* Check that the stack is large enough to grow by a single entry
- ** if the RHS of the rule is empty. This ensures that there is room
- ** enough on the stack to push the LHS value */
- if( yyRuleInfoNRhs[yyruleno]==0 ){
-#ifdef YYTRACKMAXSTACKDEPTH
- if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){
- yypParser->yyhwm++;
- assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack));
- }
-#endif
-#if YYSTACKDEPTH>0
- if( yypParser->yytos>=yypParser->yystackEnd ){
- yyStackOverflow(yypParser);
- /* The call to yyStackOverflow() above pops the stack until it is
- ** empty, causing the main parser loop to exit. So the return value
- ** is never used and does not matter. */
- return 0;
- }
-#else
- if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){
- if( yyGrowStack(yypParser) ){
- yyStackOverflow(yypParser);
- /* The call to yyStackOverflow() above pops the stack until it is
- ** empty, causing the main parser loop to exit. So the return value
- ** is never used and does not matter. */
- return 0;
- }
- yymsp = yypParser->yytos;
- }
-#endif
- }
switch( yyruleno ){
/* Beginning here are the reduction cases. A typical example
@@ -157545,16 +161552,16 @@ static YYACTIONTYPE yy_reduce(
{ sqlite3FinishCoding(pParse); }
break;
case 3: /* cmd ::= BEGIN transtype trans_opt */
-{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy192);}
+{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy376);}
break;
case 4: /* transtype ::= */
-{yymsp[1].minor.yy192 = TK_DEFERRED;}
+{yymsp[1].minor.yy376 = TK_DEFERRED;}
break;
case 5: /* transtype ::= DEFERRED */
case 6: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==6);
case 7: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==7);
- case 306: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==306);
-{yymsp[0].minor.yy192 = yymsp[0].major; /*A-overwrites-X*/}
+ case 318: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==318);
+{yymsp[0].minor.yy376 = yymsp[0].major; /*A-overwrites-X*/}
break;
case 8: /* cmd ::= COMMIT|END trans_opt */
case 9: /* cmd ::= ROLLBACK trans_opt */ yytestcase(yyruleno==9);
@@ -157577,7 +161584,7 @@ static YYACTIONTYPE yy_reduce(
break;
case 13: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */
{
- sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy192,0,0,yymsp[-2].minor.yy192);
+ sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy376,0,0,yymsp[-2].minor.yy376);
}
break;
case 14: /* createkw ::= CREATE */
@@ -157591,33 +161598,32 @@ static YYACTIONTYPE yy_reduce(
case 70: /* defer_subclause_opt ::= */ yytestcase(yyruleno==70);
case 79: /* ifexists ::= */ yytestcase(yyruleno==79);
case 96: /* distinct ::= */ yytestcase(yyruleno==96);
- case 232: /* collate ::= */ yytestcase(yyruleno==232);
-{yymsp[1].minor.yy192 = 0;}
+ case 239: /* collate ::= */ yytestcase(yyruleno==239);
+{yymsp[1].minor.yy376 = 0;}
break;
case 16: /* ifnotexists ::= IF NOT EXISTS */
-{yymsp[-2].minor.yy192 = 1;}
+{yymsp[-2].minor.yy376 = 1;}
break;
case 17: /* temp ::= TEMP */
- case 46: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==46);
-{yymsp[0].minor.yy192 = 1;}
+{yymsp[0].minor.yy376 = pParse->db->init.busy==0;}
break;
case 19: /* create_table_args ::= LP columnlist conslist_opt RP table_options */
{
- sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy192,0);
+ sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy376,0);
}
break;
case 20: /* create_table_args ::= AS select */
{
- sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy539);
- sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy539);
+ sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy81);
+ sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy81);
}
break;
case 22: /* table_options ::= WITHOUT nm */
{
if( yymsp[0].minor.yy0.n==5 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"rowid",5)==0 ){
- yymsp[-1].minor.yy192 = TF_WithoutRowid | TF_NoVisibleRowid;
+ yymsp[-1].minor.yy376 = TF_WithoutRowid | TF_NoVisibleRowid;
}else{
- yymsp[-1].minor.yy192 = 0;
+ yymsp[-1].minor.yy376 = 0;
sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z);
}
}
@@ -157646,7 +161652,7 @@ static YYACTIONTYPE yy_reduce(
case 28: /* scanpt ::= */
{
assert( yyLookahead!=YYNOCODE );
- yymsp[1].minor.yy436 = yyLookaheadToken.z;
+ yymsp[1].minor.yy504 = yyLookaheadToken.z;
}
break;
case 29: /* scantok ::= */
@@ -157660,17 +161666,17 @@ static YYACTIONTYPE yy_reduce(
{pParse->constraintName = yymsp[0].minor.yy0;}
break;
case 31: /* ccons ::= DEFAULT scantok term */
-{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy202,yymsp[-1].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);}
+{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy404,yymsp[-1].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);}
break;
case 32: /* ccons ::= DEFAULT LP expr RP */
-{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy202,yymsp[-2].minor.yy0.z+1,yymsp[0].minor.yy0.z);}
+{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy404,yymsp[-2].minor.yy0.z+1,yymsp[0].minor.yy0.z);}
break;
case 33: /* ccons ::= DEFAULT PLUS scantok term */
-{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy202,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);}
+{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy404,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);}
break;
case 34: /* ccons ::= DEFAULT MINUS scantok term */
{
- Expr *p = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy202, 0);
+ Expr *p = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy404, 0);
sqlite3AddDefaultValue(pParse,p,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);
}
break;
@@ -157685,176 +161691,161 @@ static YYACTIONTYPE yy_reduce(
}
break;
case 36: /* ccons ::= NOT NULL onconf */
-{sqlite3AddNotNull(pParse, yymsp[0].minor.yy192);}
+{sqlite3AddNotNull(pParse, yymsp[0].minor.yy376);}
break;
case 37: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */
-{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy192,yymsp[0].minor.yy192,yymsp[-2].minor.yy192);}
+{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy376,yymsp[0].minor.yy376,yymsp[-2].minor.yy376);}
break;
case 38: /* ccons ::= UNIQUE onconf */
-{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy192,0,0,0,0,
+{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy376,0,0,0,0,
SQLITE_IDXTYPE_UNIQUE);}
break;
case 39: /* ccons ::= CHECK LP expr RP */
-{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy202);}
+{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy404,yymsp[-2].minor.yy0.z,yymsp[0].minor.yy0.z);}
break;
case 40: /* ccons ::= REFERENCES nm eidlist_opt refargs */
-{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy242,yymsp[0].minor.yy192);}
+{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy70,yymsp[0].minor.yy376);}
break;
case 41: /* ccons ::= defer_subclause */
-{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy192);}
+{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy376);}
break;
case 42: /* ccons ::= COLLATE ID|STRING */
{sqlite3AddCollateType(pParse, &yymsp[0].minor.yy0);}
break;
case 43: /* generated ::= LP expr RP */
-{sqlite3AddGenerated(pParse,yymsp[-1].minor.yy202,0);}
+{sqlite3AddGenerated(pParse,yymsp[-1].minor.yy404,0);}
break;
case 44: /* generated ::= LP expr RP ID */
-{sqlite3AddGenerated(pParse,yymsp[-2].minor.yy202,&yymsp[0].minor.yy0);}
+{sqlite3AddGenerated(pParse,yymsp[-2].minor.yy404,&yymsp[0].minor.yy0);}
+ break;
+ case 46: /* autoinc ::= AUTOINCR */
+{yymsp[0].minor.yy376 = 1;}
break;
case 47: /* refargs ::= */
-{ yymsp[1].minor.yy192 = OE_None*0x0101; /* EV: R-19803-45884 */}
+{ yymsp[1].minor.yy376 = OE_None*0x0101; /* EV: R-19803-45884 */}
break;
case 48: /* refargs ::= refargs refarg */
-{ yymsp[-1].minor.yy192 = (yymsp[-1].minor.yy192 & ~yymsp[0].minor.yy207.mask) | yymsp[0].minor.yy207.value; }
+{ yymsp[-1].minor.yy376 = (yymsp[-1].minor.yy376 & ~yymsp[0].minor.yy139.mask) | yymsp[0].minor.yy139.value; }
break;
case 49: /* refarg ::= MATCH nm */
-{ yymsp[-1].minor.yy207.value = 0; yymsp[-1].minor.yy207.mask = 0x000000; }
+{ yymsp[-1].minor.yy139.value = 0; yymsp[-1].minor.yy139.mask = 0x000000; }
break;
case 50: /* refarg ::= ON INSERT refact */
-{ yymsp[-2].minor.yy207.value = 0; yymsp[-2].minor.yy207.mask = 0x000000; }
+{ yymsp[-2].minor.yy139.value = 0; yymsp[-2].minor.yy139.mask = 0x000000; }
break;
case 51: /* refarg ::= ON DELETE refact */
-{ yymsp[-2].minor.yy207.value = yymsp[0].minor.yy192; yymsp[-2].minor.yy207.mask = 0x0000ff; }
+{ yymsp[-2].minor.yy139.value = yymsp[0].minor.yy376; yymsp[-2].minor.yy139.mask = 0x0000ff; }
break;
case 52: /* refarg ::= ON UPDATE refact */
-{ yymsp[-2].minor.yy207.value = yymsp[0].minor.yy192<<8; yymsp[-2].minor.yy207.mask = 0x00ff00; }
+{ yymsp[-2].minor.yy139.value = yymsp[0].minor.yy376<<8; yymsp[-2].minor.yy139.mask = 0x00ff00; }
break;
case 53: /* refact ::= SET NULL */
-{ yymsp[-1].minor.yy192 = OE_SetNull; /* EV: R-33326-45252 */}
+{ yymsp[-1].minor.yy376 = OE_SetNull; /* EV: R-33326-45252 */}
break;
case 54: /* refact ::= SET DEFAULT */
-{ yymsp[-1].minor.yy192 = OE_SetDflt; /* EV: R-33326-45252 */}
+{ yymsp[-1].minor.yy376 = OE_SetDflt; /* EV: R-33326-45252 */}
break;
case 55: /* refact ::= CASCADE */
-{ yymsp[0].minor.yy192 = OE_Cascade; /* EV: R-33326-45252 */}
+{ yymsp[0].minor.yy376 = OE_Cascade; /* EV: R-33326-45252 */}
break;
case 56: /* refact ::= RESTRICT */
-{ yymsp[0].minor.yy192 = OE_Restrict; /* EV: R-33326-45252 */}
+{ yymsp[0].minor.yy376 = OE_Restrict; /* EV: R-33326-45252 */}
break;
case 57: /* refact ::= NO ACTION */
-{ yymsp[-1].minor.yy192 = OE_None; /* EV: R-33326-45252 */}
+{ yymsp[-1].minor.yy376 = OE_None; /* EV: R-33326-45252 */}
break;
case 58: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */
-{yymsp[-2].minor.yy192 = 0;}
+{yymsp[-2].minor.yy376 = 0;}
break;
case 59: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */
case 74: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==74);
- case 162: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==162);
-{yymsp[-1].minor.yy192 = yymsp[0].minor.yy192;}
+ case 169: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==169);
+{yymsp[-1].minor.yy376 = yymsp[0].minor.yy376;}
break;
case 61: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */
case 78: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==78);
- case 204: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==204);
- case 207: /* in_op ::= NOT IN */ yytestcase(yyruleno==207);
- case 233: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==233);
-{yymsp[-1].minor.yy192 = 1;}
+ case 211: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==211);
+ case 214: /* in_op ::= NOT IN */ yytestcase(yyruleno==214);
+ case 240: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==240);
+{yymsp[-1].minor.yy376 = 1;}
break;
case 62: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */
-{yymsp[-1].minor.yy192 = 0;}
+{yymsp[-1].minor.yy376 = 0;}
break;
case 64: /* tconscomma ::= COMMA */
{pParse->constraintName.n = 0;}
break;
case 66: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */
-{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy242,yymsp[0].minor.yy192,yymsp[-2].minor.yy192,0);}
+{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy70,yymsp[0].minor.yy376,yymsp[-2].minor.yy376,0);}
break;
case 67: /* tcons ::= UNIQUE LP sortlist RP onconf */
-{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy242,yymsp[0].minor.yy192,0,0,0,0,
+{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy70,yymsp[0].minor.yy376,0,0,0,0,
SQLITE_IDXTYPE_UNIQUE);}
break;
case 68: /* tcons ::= CHECK LP expr RP onconf */
-{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy202);}
+{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy404,yymsp[-3].minor.yy0.z,yymsp[-1].minor.yy0.z);}
break;
case 69: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */
{
- sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy242, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy242, yymsp[-1].minor.yy192);
- sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy192);
+ sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy70, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy70, yymsp[-1].minor.yy376);
+ sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy376);
}
break;
case 71: /* onconf ::= */
case 73: /* orconf ::= */ yytestcase(yyruleno==73);
-{yymsp[1].minor.yy192 = OE_Default;}
+{yymsp[1].minor.yy376 = OE_Default;}
break;
case 72: /* onconf ::= ON CONFLICT resolvetype */
-{yymsp[-2].minor.yy192 = yymsp[0].minor.yy192;}
+{yymsp[-2].minor.yy376 = yymsp[0].minor.yy376;}
break;
case 75: /* resolvetype ::= IGNORE */
-{yymsp[0].minor.yy192 = OE_Ignore;}
+{yymsp[0].minor.yy376 = OE_Ignore;}
break;
case 76: /* resolvetype ::= REPLACE */
- case 163: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==163);
-{yymsp[0].minor.yy192 = OE_Replace;}
+ case 170: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==170);
+{yymsp[0].minor.yy376 = OE_Replace;}
break;
case 77: /* cmd ::= DROP TABLE ifexists fullname */
{
- sqlite3DropTable(pParse, yymsp[0].minor.yy47, 0, yymsp[-1].minor.yy192);
+ sqlite3DropTable(pParse, yymsp[0].minor.yy153, 0, yymsp[-1].minor.yy376);
}
break;
case 80: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */
{
- sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy242, yymsp[0].minor.yy539, yymsp[-7].minor.yy192, yymsp[-5].minor.yy192);
+ sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy70, yymsp[0].minor.yy81, yymsp[-7].minor.yy376, yymsp[-5].minor.yy376);
}
break;
case 81: /* cmd ::= DROP VIEW ifexists fullname */
{
- sqlite3DropTable(pParse, yymsp[0].minor.yy47, 1, yymsp[-1].minor.yy192);
+ sqlite3DropTable(pParse, yymsp[0].minor.yy153, 1, yymsp[-1].minor.yy376);
}
break;
case 82: /* cmd ::= select */
{
SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0};
- sqlite3Select(pParse, yymsp[0].minor.yy539, &dest);
- sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy539);
+ sqlite3Select(pParse, yymsp[0].minor.yy81, &dest);
+ sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy81);
}
break;
case 83: /* select ::= WITH wqlist selectnowith */
-{
- Select *p = yymsp[0].minor.yy539;
- if( p ){
- p->pWith = yymsp[-1].minor.yy131;
- parserDoubleLinkSelect(pParse, p);
- }else{
- sqlite3WithDelete(pParse->db, yymsp[-1].minor.yy131);
- }
- yymsp[-2].minor.yy539 = p;
-}
+{yymsp[-2].minor.yy81 = attachWithToSelect(pParse,yymsp[0].minor.yy81,yymsp[-1].minor.yy103);}
break;
case 84: /* select ::= WITH RECURSIVE wqlist selectnowith */
-{
- Select *p = yymsp[0].minor.yy539;
- if( p ){
- p->pWith = yymsp[-1].minor.yy131;
- parserDoubleLinkSelect(pParse, p);
- }else{
- sqlite3WithDelete(pParse->db, yymsp[-1].minor.yy131);
- }
- yymsp[-3].minor.yy539 = p;
-}
+{yymsp[-3].minor.yy81 = attachWithToSelect(pParse,yymsp[0].minor.yy81,yymsp[-1].minor.yy103);}
break;
case 85: /* select ::= selectnowith */
{
- Select *p = yymsp[0].minor.yy539;
+ Select *p = yymsp[0].minor.yy81;
if( p ){
parserDoubleLinkSelect(pParse, p);
}
- yymsp[0].minor.yy539 = p; /*A-overwrites-X*/
+ yymsp[0].minor.yy81 = p; /*A-overwrites-X*/
}
break;
case 86: /* selectnowith ::= selectnowith multiselect_op oneselect */
{
- Select *pRhs = yymsp[0].minor.yy539;
- Select *pLhs = yymsp[-2].minor.yy539;
+ Select *pRhs = yymsp[0].minor.yy81;
+ Select *pLhs = yymsp[-2].minor.yy81;
if( pRhs && pRhs->pPrior ){
SrcList *pFrom;
Token x;
@@ -157864,83 +161855,83 @@ static YYACTIONTYPE yy_reduce(
pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0);
}
if( pRhs ){
- pRhs->op = (u8)yymsp[-1].minor.yy192;
+ pRhs->op = (u8)yymsp[-1].minor.yy376;
pRhs->pPrior = pLhs;
if( ALWAYS(pLhs) ) pLhs->selFlags &= ~SF_MultiValue;
pRhs->selFlags &= ~SF_MultiValue;
- if( yymsp[-1].minor.yy192!=TK_ALL ) pParse->hasCompound = 1;
+ if( yymsp[-1].minor.yy376!=TK_ALL ) pParse->hasCompound = 1;
}else{
sqlite3SelectDelete(pParse->db, pLhs);
}
- yymsp[-2].minor.yy539 = pRhs;
+ yymsp[-2].minor.yy81 = pRhs;
}
break;
case 87: /* multiselect_op ::= UNION */
case 89: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==89);
-{yymsp[0].minor.yy192 = yymsp[0].major; /*A-overwrites-OP*/}
+{yymsp[0].minor.yy376 = yymsp[0].major; /*A-overwrites-OP*/}
break;
case 88: /* multiselect_op ::= UNION ALL */
-{yymsp[-1].minor.yy192 = TK_ALL;}
+{yymsp[-1].minor.yy376 = TK_ALL;}
break;
case 90: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */
{
- yymsp[-8].minor.yy539 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy242,yymsp[-5].minor.yy47,yymsp[-4].minor.yy202,yymsp[-3].minor.yy242,yymsp[-2].minor.yy202,yymsp[-1].minor.yy242,yymsp[-7].minor.yy192,yymsp[0].minor.yy202);
+ yymsp[-8].minor.yy81 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy70,yymsp[-5].minor.yy153,yymsp[-4].minor.yy404,yymsp[-3].minor.yy70,yymsp[-2].minor.yy404,yymsp[-1].minor.yy70,yymsp[-7].minor.yy376,yymsp[0].minor.yy404);
}
break;
case 91: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */
{
- yymsp[-9].minor.yy539 = sqlite3SelectNew(pParse,yymsp[-7].minor.yy242,yymsp[-6].minor.yy47,yymsp[-5].minor.yy202,yymsp[-4].minor.yy242,yymsp[-3].minor.yy202,yymsp[-1].minor.yy242,yymsp[-8].minor.yy192,yymsp[0].minor.yy202);
- if( yymsp[-9].minor.yy539 ){
- yymsp[-9].minor.yy539->pWinDefn = yymsp[-2].minor.yy303;
+ yymsp[-9].minor.yy81 = sqlite3SelectNew(pParse,yymsp[-7].minor.yy70,yymsp[-6].minor.yy153,yymsp[-5].minor.yy404,yymsp[-4].minor.yy70,yymsp[-3].minor.yy404,yymsp[-1].minor.yy70,yymsp[-8].minor.yy376,yymsp[0].minor.yy404);
+ if( yymsp[-9].minor.yy81 ){
+ yymsp[-9].minor.yy81->pWinDefn = yymsp[-2].minor.yy49;
}else{
- sqlite3WindowListDelete(pParse->db, yymsp[-2].minor.yy303);
+ sqlite3WindowListDelete(pParse->db, yymsp[-2].minor.yy49);
}
}
break;
case 92: /* values ::= VALUES LP nexprlist RP */
{
- yymsp[-3].minor.yy539 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy242,0,0,0,0,0,SF_Values,0);
+ yymsp[-3].minor.yy81 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy70,0,0,0,0,0,SF_Values,0);
}
break;
case 93: /* values ::= values COMMA LP nexprlist RP */
{
- Select *pRight, *pLeft = yymsp[-4].minor.yy539;
- pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy242,0,0,0,0,0,SF_Values|SF_MultiValue,0);
+ Select *pRight, *pLeft = yymsp[-4].minor.yy81;
+ pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy70,0,0,0,0,0,SF_Values|SF_MultiValue,0);
if( ALWAYS(pLeft) ) pLeft->selFlags &= ~SF_MultiValue;
if( pRight ){
pRight->op = TK_ALL;
pRight->pPrior = pLeft;
- yymsp[-4].minor.yy539 = pRight;
+ yymsp[-4].minor.yy81 = pRight;
}else{
- yymsp[-4].minor.yy539 = pLeft;
+ yymsp[-4].minor.yy81 = pLeft;
}
}
break;
case 94: /* distinct ::= DISTINCT */
-{yymsp[0].minor.yy192 = SF_Distinct;}
+{yymsp[0].minor.yy376 = SF_Distinct;}
break;
case 95: /* distinct ::= ALL */
-{yymsp[0].minor.yy192 = SF_All;}
+{yymsp[0].minor.yy376 = SF_All;}
break;
case 97: /* sclp ::= */
case 130: /* orderby_opt ::= */ yytestcase(yyruleno==130);
case 140: /* groupby_opt ::= */ yytestcase(yyruleno==140);
- case 220: /* exprlist ::= */ yytestcase(yyruleno==220);
- case 223: /* paren_exprlist ::= */ yytestcase(yyruleno==223);
- case 228: /* eidlist_opt ::= */ yytestcase(yyruleno==228);
-{yymsp[1].minor.yy242 = 0;}
+ case 227: /* exprlist ::= */ yytestcase(yyruleno==227);
+ case 230: /* paren_exprlist ::= */ yytestcase(yyruleno==230);
+ case 235: /* eidlist_opt ::= */ yytestcase(yyruleno==235);
+{yymsp[1].minor.yy70 = 0;}
break;
case 98: /* selcollist ::= sclp scanpt expr scanpt as */
{
- yymsp[-4].minor.yy242 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy242, yymsp[-2].minor.yy202);
- if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy242, &yymsp[0].minor.yy0, 1);
- sqlite3ExprListSetSpan(pParse,yymsp[-4].minor.yy242,yymsp[-3].minor.yy436,yymsp[-1].minor.yy436);
+ yymsp[-4].minor.yy70 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy70, yymsp[-2].minor.yy404);
+ if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy70, &yymsp[0].minor.yy0, 1);
+ sqlite3ExprListSetSpan(pParse,yymsp[-4].minor.yy70,yymsp[-3].minor.yy504,yymsp[-1].minor.yy504);
}
break;
case 99: /* selcollist ::= sclp scanpt STAR */
{
Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0);
- yymsp[-2].minor.yy242 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy242, p);
+ yymsp[-2].minor.yy70 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy70, p);
}
break;
case 100: /* selcollist ::= sclp scanpt nm DOT STAR */
@@ -157948,56 +161939,56 @@ static YYACTIONTYPE yy_reduce(
Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0);
Expr *pLeft = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1);
Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight);
- yymsp[-4].minor.yy242 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy242, pDot);
+ yymsp[-4].minor.yy70 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy70, pDot);
}
break;
case 101: /* as ::= AS nm */
case 112: /* dbnm ::= DOT nm */ yytestcase(yyruleno==112);
- case 244: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==244);
- case 245: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==245);
+ case 251: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==251);
+ case 252: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==252);
{yymsp[-1].minor.yy0 = yymsp[0].minor.yy0;}
break;
case 103: /* from ::= */
case 106: /* stl_prefix ::= */ yytestcase(yyruleno==106);
-{yymsp[1].minor.yy47 = 0;}
+{yymsp[1].minor.yy153 = 0;}
break;
case 104: /* from ::= FROM seltablist */
{
- yymsp[-1].minor.yy47 = yymsp[0].minor.yy47;
- sqlite3SrcListShiftJoinType(yymsp[-1].minor.yy47);
+ yymsp[-1].minor.yy153 = yymsp[0].minor.yy153;
+ sqlite3SrcListShiftJoinType(yymsp[-1].minor.yy153);
}
break;
case 105: /* stl_prefix ::= seltablist joinop */
{
- if( ALWAYS(yymsp[-1].minor.yy47 && yymsp[-1].minor.yy47->nSrc>0) ) yymsp[-1].minor.yy47->a[yymsp[-1].minor.yy47->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy192;
+ if( ALWAYS(yymsp[-1].minor.yy153 && yymsp[-1].minor.yy153->nSrc>0) ) yymsp[-1].minor.yy153->a[yymsp[-1].minor.yy153->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy376;
}
break;
case 107: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */
{
- yymsp[-6].minor.yy47 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy47,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy202,yymsp[0].minor.yy600);
- sqlite3SrcListIndexedBy(pParse, yymsp[-6].minor.yy47, &yymsp[-2].minor.yy0);
+ yymsp[-6].minor.yy153 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy153,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy404,yymsp[0].minor.yy436);
+ sqlite3SrcListIndexedBy(pParse, yymsp[-6].minor.yy153, &yymsp[-2].minor.yy0);
}
break;
case 108: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */
{
- yymsp[-8].minor.yy47 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-8].minor.yy47,&yymsp[-7].minor.yy0,&yymsp[-6].minor.yy0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy202,yymsp[0].minor.yy600);
- sqlite3SrcListFuncArgs(pParse, yymsp[-8].minor.yy47, yymsp[-4].minor.yy242);
+ yymsp[-8].minor.yy153 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-8].minor.yy153,&yymsp[-7].minor.yy0,&yymsp[-6].minor.yy0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy404,yymsp[0].minor.yy436);
+ sqlite3SrcListFuncArgs(pParse, yymsp[-8].minor.yy153, yymsp[-4].minor.yy70);
}
break;
case 109: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */
{
- yymsp[-6].minor.yy47 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy47,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy539,yymsp[-1].minor.yy202,yymsp[0].minor.yy600);
+ yymsp[-6].minor.yy153 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy153,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy81,yymsp[-1].minor.yy404,yymsp[0].minor.yy436);
}
break;
case 110: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */
{
- if( yymsp[-6].minor.yy47==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy202==0 && yymsp[0].minor.yy600==0 ){
- yymsp[-6].minor.yy47 = yymsp[-4].minor.yy47;
- }else if( yymsp[-4].minor.yy47->nSrc==1 ){
- yymsp[-6].minor.yy47 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy47,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy202,yymsp[0].minor.yy600);
- if( yymsp[-6].minor.yy47 ){
- struct SrcList_item *pNew = &yymsp[-6].minor.yy47->a[yymsp[-6].minor.yy47->nSrc-1];
- struct SrcList_item *pOld = yymsp[-4].minor.yy47->a;
+ if( yymsp[-6].minor.yy153==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy404==0 && yymsp[0].minor.yy436==0 ){
+ yymsp[-6].minor.yy153 = yymsp[-4].minor.yy153;
+ }else if( yymsp[-4].minor.yy153->nSrc==1 ){
+ yymsp[-6].minor.yy153 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy153,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy404,yymsp[0].minor.yy436);
+ if( yymsp[-6].minor.yy153 ){
+ SrcItem *pNew = &yymsp[-6].minor.yy153->a[yymsp[-6].minor.yy153->nSrc-1];
+ SrcItem *pOld = yymsp[-4].minor.yy153->a;
pNew->zName = pOld->zName;
pNew->zDatabase = pOld->zDatabase;
pNew->pSelect = pOld->pSelect;
@@ -158010,12 +162001,12 @@ static YYACTIONTYPE yy_reduce(
pOld->zName = pOld->zDatabase = 0;
pOld->pSelect = 0;
}
- sqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy47);
+ sqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy153);
}else{
Select *pSubquery;
- sqlite3SrcListShiftJoinType(yymsp[-4].minor.yy47);
- pSubquery = sqlite3SelectNew(pParse,0,yymsp[-4].minor.yy47,0,0,0,0,SF_NestedFrom,0);
- yymsp[-6].minor.yy47 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy47,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy202,yymsp[0].minor.yy600);
+ sqlite3SrcListShiftJoinType(yymsp[-4].minor.yy153);
+ pSubquery = sqlite3SelectNew(pParse,0,yymsp[-4].minor.yy153,0,0,0,0,SF_NestedFrom,0);
+ yymsp[-6].minor.yy153 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy153,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy404,yymsp[0].minor.yy436);
}
}
break;
@@ -158025,63 +162016,65 @@ static YYACTIONTYPE yy_reduce(
break;
case 113: /* fullname ::= nm */
{
- yylhsminor.yy47 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0);
- if( IN_RENAME_OBJECT && yylhsminor.yy47 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy47->a[0].zName, &yymsp[0].minor.yy0);
+ yylhsminor.yy153 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0);
+ if( IN_RENAME_OBJECT && yylhsminor.yy153 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy153->a[0].zName, &yymsp[0].minor.yy0);
}
- yymsp[0].minor.yy47 = yylhsminor.yy47;
+ yymsp[0].minor.yy153 = yylhsminor.yy153;
break;
case 114: /* fullname ::= nm DOT nm */
{
- yylhsminor.yy47 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0);
- if( IN_RENAME_OBJECT && yylhsminor.yy47 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy47->a[0].zName, &yymsp[0].minor.yy0);
+ yylhsminor.yy153 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0);
+ if( IN_RENAME_OBJECT && yylhsminor.yy153 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy153->a[0].zName, &yymsp[0].minor.yy0);
}
- yymsp[-2].minor.yy47 = yylhsminor.yy47;
+ yymsp[-2].minor.yy153 = yylhsminor.yy153;
break;
case 115: /* xfullname ::= nm */
-{yymsp[0].minor.yy47 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); /*A-overwrites-X*/}
+{yymsp[0].minor.yy153 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); /*A-overwrites-X*/}
break;
case 116: /* xfullname ::= nm DOT nm */
-{yymsp[-2].minor.yy47 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/}
+{yymsp[-2].minor.yy153 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/}
break;
case 117: /* xfullname ::= nm DOT nm AS nm */
{
- yymsp[-4].minor.yy47 = sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); /*A-overwrites-X*/
- if( yymsp[-4].minor.yy47 ) yymsp[-4].minor.yy47->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0);
+ yymsp[-4].minor.yy153 = sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); /*A-overwrites-X*/
+ if( yymsp[-4].minor.yy153 ) yymsp[-4].minor.yy153->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0);
}
break;
case 118: /* xfullname ::= nm AS nm */
{
- yymsp[-2].minor.yy47 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); /*A-overwrites-X*/
- if( yymsp[-2].minor.yy47 ) yymsp[-2].minor.yy47->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0);
+ yymsp[-2].minor.yy153 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); /*A-overwrites-X*/
+ if( yymsp[-2].minor.yy153 ) yymsp[-2].minor.yy153->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0);
}
break;
case 119: /* joinop ::= COMMA|JOIN */
-{ yymsp[0].minor.yy192 = JT_INNER; }
+{ yymsp[0].minor.yy376 = JT_INNER; }
break;
case 120: /* joinop ::= JOIN_KW JOIN */
-{yymsp[-1].minor.yy192 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/}
+{yymsp[-1].minor.yy376 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/}
break;
case 121: /* joinop ::= JOIN_KW nm JOIN */
-{yymsp[-2].minor.yy192 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/}
+{yymsp[-2].minor.yy376 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/}
break;
case 122: /* joinop ::= JOIN_KW nm nm JOIN */
-{yymsp[-3].minor.yy192 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/}
+{yymsp[-3].minor.yy376 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/}
break;
case 123: /* on_opt ::= ON expr */
case 143: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==143);
case 150: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==150);
- case 216: /* case_else ::= ELSE expr */ yytestcase(yyruleno==216);
- case 237: /* vinto ::= INTO expr */ yytestcase(yyruleno==237);
-{yymsp[-1].minor.yy202 = yymsp[0].minor.yy202;}
+ case 152: /* where_opt_ret ::= WHERE expr */ yytestcase(yyruleno==152);
+ case 223: /* case_else ::= ELSE expr */ yytestcase(yyruleno==223);
+ case 244: /* vinto ::= INTO expr */ yytestcase(yyruleno==244);
+{yymsp[-1].minor.yy404 = yymsp[0].minor.yy404;}
break;
case 124: /* on_opt ::= */
case 142: /* having_opt ::= */ yytestcase(yyruleno==142);
case 144: /* limit_opt ::= */ yytestcase(yyruleno==144);
case 149: /* where_opt ::= */ yytestcase(yyruleno==149);
- case 217: /* case_else ::= */ yytestcase(yyruleno==217);
- case 219: /* case_operand ::= */ yytestcase(yyruleno==219);
- case 238: /* vinto ::= */ yytestcase(yyruleno==238);
-{yymsp[1].minor.yy202 = 0;}
+ case 151: /* where_opt_ret ::= */ yytestcase(yyruleno==151);
+ case 224: /* case_else ::= */ yytestcase(yyruleno==224);
+ case 226: /* case_operand ::= */ yytestcase(yyruleno==226);
+ case 245: /* vinto ::= */ yytestcase(yyruleno==245);
+{yymsp[1].minor.yy404 = 0;}
break;
case 126: /* indexed_opt ::= INDEXED BY nm */
{yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;}
@@ -158090,129 +162083,144 @@ static YYACTIONTYPE yy_reduce(
{yymsp[-1].minor.yy0.z=0; yymsp[-1].minor.yy0.n=1;}
break;
case 128: /* using_opt ::= USING LP idlist RP */
-{yymsp[-3].minor.yy600 = yymsp[-1].minor.yy600;}
+{yymsp[-3].minor.yy436 = yymsp[-1].minor.yy436;}
break;
case 129: /* using_opt ::= */
- case 164: /* idlist_opt ::= */ yytestcase(yyruleno==164);
-{yymsp[1].minor.yy600 = 0;}
+ case 171: /* idlist_opt ::= */ yytestcase(yyruleno==171);
+{yymsp[1].minor.yy436 = 0;}
break;
case 131: /* orderby_opt ::= ORDER BY sortlist */
case 141: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==141);
-{yymsp[-2].minor.yy242 = yymsp[0].minor.yy242;}
+{yymsp[-2].minor.yy70 = yymsp[0].minor.yy70;}
break;
case 132: /* sortlist ::= sortlist COMMA expr sortorder nulls */
{
- yymsp[-4].minor.yy242 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy242,yymsp[-2].minor.yy202);
- sqlite3ExprListSetSortOrder(yymsp[-4].minor.yy242,yymsp[-1].minor.yy192,yymsp[0].minor.yy192);
+ yymsp[-4].minor.yy70 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy70,yymsp[-2].minor.yy404);
+ sqlite3ExprListSetSortOrder(yymsp[-4].minor.yy70,yymsp[-1].minor.yy376,yymsp[0].minor.yy376);
}
break;
case 133: /* sortlist ::= expr sortorder nulls */
{
- yymsp[-2].minor.yy242 = sqlite3ExprListAppend(pParse,0,yymsp[-2].minor.yy202); /*A-overwrites-Y*/
- sqlite3ExprListSetSortOrder(yymsp[-2].minor.yy242,yymsp[-1].minor.yy192,yymsp[0].minor.yy192);
+ yymsp[-2].minor.yy70 = sqlite3ExprListAppend(pParse,0,yymsp[-2].minor.yy404); /*A-overwrites-Y*/
+ sqlite3ExprListSetSortOrder(yymsp[-2].minor.yy70,yymsp[-1].minor.yy376,yymsp[0].minor.yy376);
}
break;
case 134: /* sortorder ::= ASC */
-{yymsp[0].minor.yy192 = SQLITE_SO_ASC;}
+{yymsp[0].minor.yy376 = SQLITE_SO_ASC;}
break;
case 135: /* sortorder ::= DESC */
-{yymsp[0].minor.yy192 = SQLITE_SO_DESC;}
+{yymsp[0].minor.yy376 = SQLITE_SO_DESC;}
break;
case 136: /* sortorder ::= */
case 139: /* nulls ::= */ yytestcase(yyruleno==139);
-{yymsp[1].minor.yy192 = SQLITE_SO_UNDEFINED;}
+{yymsp[1].minor.yy376 = SQLITE_SO_UNDEFINED;}
break;
case 137: /* nulls ::= NULLS FIRST */
-{yymsp[-1].minor.yy192 = SQLITE_SO_ASC;}
+{yymsp[-1].minor.yy376 = SQLITE_SO_ASC;}
break;
case 138: /* nulls ::= NULLS LAST */
-{yymsp[-1].minor.yy192 = SQLITE_SO_DESC;}
+{yymsp[-1].minor.yy376 = SQLITE_SO_DESC;}
break;
case 145: /* limit_opt ::= LIMIT expr */
-{yymsp[-1].minor.yy202 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy202,0);}
+{yymsp[-1].minor.yy404 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy404,0);}
break;
case 146: /* limit_opt ::= LIMIT expr OFFSET expr */
-{yymsp[-3].minor.yy202 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[-2].minor.yy202,yymsp[0].minor.yy202);}
+{yymsp[-3].minor.yy404 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[-2].minor.yy404,yymsp[0].minor.yy404);}
break;
case 147: /* limit_opt ::= LIMIT expr COMMA expr */
-{yymsp[-3].minor.yy202 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy202,yymsp[-2].minor.yy202);}
+{yymsp[-3].minor.yy404 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy404,yymsp[-2].minor.yy404);}
break;
- case 148: /* cmd ::= with DELETE FROM xfullname indexed_opt where_opt */
+ case 148: /* cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */
{
- sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy47, &yymsp[-1].minor.yy0);
- sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy47,yymsp[0].minor.yy202,0,0);
+ sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy153, &yymsp[-1].minor.yy0);
+ sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy153,yymsp[0].minor.yy404,0,0);
}
break;
- case 151: /* cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt */
+ case 153: /* where_opt_ret ::= RETURNING selcollist */
+{sqlite3AddReturning(pParse,yymsp[0].minor.yy70); yymsp[-1].minor.yy404 = 0;}
+ break;
+ case 154: /* where_opt_ret ::= WHERE expr RETURNING selcollist */
+{sqlite3AddReturning(pParse,yymsp[0].minor.yy70); yymsp[-3].minor.yy404 = yymsp[-2].minor.yy404;}
+ break;
+ case 155: /* cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */
{
- sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy47, &yymsp[-4].minor.yy0);
- sqlite3ExprListCheckLength(pParse,yymsp[-2].minor.yy242,"set list");
- yymsp[-5].minor.yy47 = sqlite3SrcListAppendList(pParse, yymsp[-5].minor.yy47, yymsp[-1].minor.yy47);
- sqlite3Update(pParse,yymsp[-5].minor.yy47,yymsp[-2].minor.yy242,yymsp[0].minor.yy202,yymsp[-6].minor.yy192,0,0,0);
+ sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy153, &yymsp[-4].minor.yy0);
+ sqlite3ExprListCheckLength(pParse,yymsp[-2].minor.yy70,"set list");
+ yymsp[-5].minor.yy153 = sqlite3SrcListAppendList(pParse, yymsp[-5].minor.yy153, yymsp[-1].minor.yy153);
+ sqlite3Update(pParse,yymsp[-5].minor.yy153,yymsp[-2].minor.yy70,yymsp[0].minor.yy404,yymsp[-6].minor.yy376,0,0,0);
}
break;
- case 152: /* setlist ::= setlist COMMA nm EQ expr */
+ case 156: /* setlist ::= setlist COMMA nm EQ expr */
{
- yymsp[-4].minor.yy242 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy242, yymsp[0].minor.yy202);
- sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy242, &yymsp[-2].minor.yy0, 1);
+ yymsp[-4].minor.yy70 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy70, yymsp[0].minor.yy404);
+ sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy70, &yymsp[-2].minor.yy0, 1);
}
break;
- case 153: /* setlist ::= setlist COMMA LP idlist RP EQ expr */
+ case 157: /* setlist ::= setlist COMMA LP idlist RP EQ expr */
{
- yymsp[-6].minor.yy242 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy242, yymsp[-3].minor.yy600, yymsp[0].minor.yy202);
+ yymsp[-6].minor.yy70 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy70, yymsp[-3].minor.yy436, yymsp[0].minor.yy404);
}
break;
- case 154: /* setlist ::= nm EQ expr */
+ case 158: /* setlist ::= nm EQ expr */
{
- yylhsminor.yy242 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy202);
- sqlite3ExprListSetName(pParse, yylhsminor.yy242, &yymsp[-2].minor.yy0, 1);
+ yylhsminor.yy70 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy404);
+ sqlite3ExprListSetName(pParse, yylhsminor.yy70, &yymsp[-2].minor.yy0, 1);
}
- yymsp[-2].minor.yy242 = yylhsminor.yy242;
+ yymsp[-2].minor.yy70 = yylhsminor.yy70;
break;
- case 155: /* setlist ::= LP idlist RP EQ expr */
+ case 159: /* setlist ::= LP idlist RP EQ expr */
{
- yymsp[-4].minor.yy242 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy600, yymsp[0].minor.yy202);
+ yymsp[-4].minor.yy70 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy436, yymsp[0].minor.yy404);
}
break;
- case 156: /* cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */
+ case 160: /* cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */
{
- sqlite3Insert(pParse, yymsp[-3].minor.yy47, yymsp[-1].minor.yy539, yymsp[-2].minor.yy600, yymsp[-5].minor.yy192, yymsp[0].minor.yy318);
+ sqlite3Insert(pParse, yymsp[-3].minor.yy153, yymsp[-1].minor.yy81, yymsp[-2].minor.yy436, yymsp[-5].minor.yy376, yymsp[0].minor.yy190);
}
break;
- case 157: /* cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES */
+ case 161: /* cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */
{
- sqlite3Insert(pParse, yymsp[-3].minor.yy47, 0, yymsp[-2].minor.yy600, yymsp[-5].minor.yy192, 0);
+ sqlite3Insert(pParse, yymsp[-4].minor.yy153, 0, yymsp[-3].minor.yy436, yymsp[-6].minor.yy376, 0);
}
break;
- case 158: /* upsert ::= */
-{ yymsp[1].minor.yy318 = 0; }
+ case 162: /* upsert ::= */
+{ yymsp[1].minor.yy190 = 0; }
+ break;
+ case 163: /* upsert ::= RETURNING selcollist */
+{ yymsp[-1].minor.yy190 = 0; sqlite3AddReturning(pParse,yymsp[0].minor.yy70); }
+ break;
+ case 164: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */
+{ yymsp[-11].minor.yy190 = sqlite3UpsertNew(pParse->db,yymsp[-8].minor.yy70,yymsp[-6].minor.yy404,yymsp[-2].minor.yy70,yymsp[-1].minor.yy404,yymsp[0].minor.yy190);}
break;
- case 159: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt */
-{ yymsp[-10].minor.yy318 = sqlite3UpsertNew(pParse->db,yymsp[-7].minor.yy242,yymsp[-5].minor.yy202,yymsp[-1].minor.yy242,yymsp[0].minor.yy202);}
+ case 165: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */
+{ yymsp[-8].minor.yy190 = sqlite3UpsertNew(pParse->db,yymsp[-5].minor.yy70,yymsp[-3].minor.yy404,0,0,yymsp[0].minor.yy190); }
break;
- case 160: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING */
-{ yymsp[-7].minor.yy318 = sqlite3UpsertNew(pParse->db,yymsp[-4].minor.yy242,yymsp[-2].minor.yy202,0,0); }
+ case 166: /* upsert ::= ON CONFLICT DO NOTHING returning */
+{ yymsp[-4].minor.yy190 = sqlite3UpsertNew(pParse->db,0,0,0,0,0); }
break;
- case 161: /* upsert ::= ON CONFLICT DO NOTHING */
-{ yymsp[-3].minor.yy318 = sqlite3UpsertNew(pParse->db,0,0,0,0); }
+ case 167: /* upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */
+{ yymsp[-7].minor.yy190 = sqlite3UpsertNew(pParse->db,0,0,yymsp[-2].minor.yy70,yymsp[-1].minor.yy404,0);}
break;
- case 165: /* idlist_opt ::= LP idlist RP */
-{yymsp[-2].minor.yy600 = yymsp[-1].minor.yy600;}
+ case 168: /* returning ::= RETURNING selcollist */
+{sqlite3AddReturning(pParse,yymsp[0].minor.yy70);}
break;
- case 166: /* idlist ::= idlist COMMA nm */
-{yymsp[-2].minor.yy600 = sqlite3IdListAppend(pParse,yymsp[-2].minor.yy600,&yymsp[0].minor.yy0);}
+ case 172: /* idlist_opt ::= LP idlist RP */
+{yymsp[-2].minor.yy436 = yymsp[-1].minor.yy436;}
break;
- case 167: /* idlist ::= nm */
-{yymsp[0].minor.yy600 = sqlite3IdListAppend(pParse,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/}
+ case 173: /* idlist ::= idlist COMMA nm */
+{yymsp[-2].minor.yy436 = sqlite3IdListAppend(pParse,yymsp[-2].minor.yy436,&yymsp[0].minor.yy0);}
break;
- case 168: /* expr ::= LP expr RP */
-{yymsp[-2].minor.yy202 = yymsp[-1].minor.yy202;}
+ case 174: /* idlist ::= nm */
+{yymsp[0].minor.yy436 = sqlite3IdListAppend(pParse,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/}
break;
- case 169: /* expr ::= ID|INDEXED */
- case 170: /* expr ::= JOIN_KW */ yytestcase(yyruleno==170);
-{yymsp[0].minor.yy202=tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/}
+ case 175: /* expr ::= LP expr RP */
+{yymsp[-2].minor.yy404 = yymsp[-1].minor.yy404;}
break;
- case 171: /* expr ::= nm DOT nm */
+ case 176: /* expr ::= ID|INDEXED */
+ case 177: /* expr ::= JOIN_KW */ yytestcase(yyruleno==177);
+{yymsp[0].minor.yy404=tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/}
+ break;
+ case 178: /* expr ::= nm DOT nm */
{
Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1);
Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1);
@@ -158220,11 +162228,11 @@ static YYACTIONTYPE yy_reduce(
sqlite3RenameTokenMap(pParse, (void*)temp2, &yymsp[0].minor.yy0);
sqlite3RenameTokenMap(pParse, (void*)temp1, &yymsp[-2].minor.yy0);
}
- yylhsminor.yy202 = sqlite3PExpr(pParse, TK_DOT, temp1, temp2);
+ yylhsminor.yy404 = sqlite3PExpr(pParse, TK_DOT, temp1, temp2);
}
- yymsp[-2].minor.yy202 = yylhsminor.yy202;
+ yymsp[-2].minor.yy404 = yylhsminor.yy404;
break;
- case 172: /* expr ::= nm DOT nm DOT nm */
+ case 179: /* expr ::= nm DOT nm DOT nm */
{
Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-4].minor.yy0, 1);
Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1);
@@ -158234,26 +162242,26 @@ static YYACTIONTYPE yy_reduce(
sqlite3RenameTokenMap(pParse, (void*)temp3, &yymsp[0].minor.yy0);
sqlite3RenameTokenMap(pParse, (void*)temp2, &yymsp[-2].minor.yy0);
}
- yylhsminor.yy202 = sqlite3PExpr(pParse, TK_DOT, temp1, temp4);
+ yylhsminor.yy404 = sqlite3PExpr(pParse, TK_DOT, temp1, temp4);
}
- yymsp[-4].minor.yy202 = yylhsminor.yy202;
+ yymsp[-4].minor.yy404 = yylhsminor.yy404;
break;
- case 173: /* term ::= NULL|FLOAT|BLOB */
- case 174: /* term ::= STRING */ yytestcase(yyruleno==174);
-{yymsp[0].minor.yy202=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/}
+ case 180: /* term ::= NULL|FLOAT|BLOB */
+ case 181: /* term ::= STRING */ yytestcase(yyruleno==181);
+{yymsp[0].minor.yy404=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/}
break;
- case 175: /* term ::= INTEGER */
+ case 182: /* term ::= INTEGER */
{
- yylhsminor.yy202 = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1);
+ yylhsminor.yy404 = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1);
}
- yymsp[0].minor.yy202 = yylhsminor.yy202;
+ yymsp[0].minor.yy404 = yylhsminor.yy404;
break;
- case 176: /* expr ::= VARIABLE */
+ case 183: /* expr ::= VARIABLE */
{
if( !(yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1])) ){
u32 n = yymsp[0].minor.yy0.n;
- yymsp[0].minor.yy202 = tokenExpr(pParse, TK_VARIABLE, yymsp[0].minor.yy0);
- sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy202, n);
+ yymsp[0].minor.yy404 = tokenExpr(pParse, TK_VARIABLE, yymsp[0].minor.yy0);
+ sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy404, n);
}else{
/* When doing a nested parse, one can include terms in an expression
** that look like this: #1 #2 ... These terms refer to registers
@@ -158262,159 +162270,159 @@ static YYACTIONTYPE yy_reduce(
assert( t.n>=2 );
if( pParse->nested==0 ){
sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t);
- yymsp[0].minor.yy202 = 0;
+ yymsp[0].minor.yy404 = 0;
}else{
- yymsp[0].minor.yy202 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0);
- if( yymsp[0].minor.yy202 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy202->iTable);
+ yymsp[0].minor.yy404 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0);
+ if( yymsp[0].minor.yy404 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy404->iTable);
}
}
}
break;
- case 177: /* expr ::= expr COLLATE ID|STRING */
+ case 184: /* expr ::= expr COLLATE ID|STRING */
{
- yymsp[-2].minor.yy202 = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy202, &yymsp[0].minor.yy0, 1);
+ yymsp[-2].minor.yy404 = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy404, &yymsp[0].minor.yy0, 1);
}
break;
- case 178: /* expr ::= CAST LP expr AS typetoken RP */
+ case 185: /* expr ::= CAST LP expr AS typetoken RP */
{
- yymsp[-5].minor.yy202 = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1);
- sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy202, yymsp[-3].minor.yy202, 0);
+ yymsp[-5].minor.yy404 = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1);
+ sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy404, yymsp[-3].minor.yy404, 0);
}
break;
- case 179: /* expr ::= ID|INDEXED LP distinct exprlist RP */
+ case 186: /* expr ::= ID|INDEXED LP distinct exprlist RP */
{
- yylhsminor.yy202 = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy242, &yymsp[-4].minor.yy0, yymsp[-2].minor.yy192);
+ yylhsminor.yy404 = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy70, &yymsp[-4].minor.yy0, yymsp[-2].minor.yy376);
}
- yymsp[-4].minor.yy202 = yylhsminor.yy202;
+ yymsp[-4].minor.yy404 = yylhsminor.yy404;
break;
- case 180: /* expr ::= ID|INDEXED LP STAR RP */
+ case 187: /* expr ::= ID|INDEXED LP STAR RP */
{
- yylhsminor.yy202 = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0, 0);
+ yylhsminor.yy404 = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0, 0);
}
- yymsp[-3].minor.yy202 = yylhsminor.yy202;
+ yymsp[-3].minor.yy404 = yylhsminor.yy404;
break;
- case 181: /* expr ::= ID|INDEXED LP distinct exprlist RP filter_over */
+ case 188: /* expr ::= ID|INDEXED LP distinct exprlist RP filter_over */
{
- yylhsminor.yy202 = sqlite3ExprFunction(pParse, yymsp[-2].minor.yy242, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy192);
- sqlite3WindowAttach(pParse, yylhsminor.yy202, yymsp[0].minor.yy303);
+ yylhsminor.yy404 = sqlite3ExprFunction(pParse, yymsp[-2].minor.yy70, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy376);
+ sqlite3WindowAttach(pParse, yylhsminor.yy404, yymsp[0].minor.yy49);
}
- yymsp[-5].minor.yy202 = yylhsminor.yy202;
+ yymsp[-5].minor.yy404 = yylhsminor.yy404;
break;
- case 182: /* expr ::= ID|INDEXED LP STAR RP filter_over */
+ case 189: /* expr ::= ID|INDEXED LP STAR RP filter_over */
{
- yylhsminor.yy202 = sqlite3ExprFunction(pParse, 0, &yymsp[-4].minor.yy0, 0);
- sqlite3WindowAttach(pParse, yylhsminor.yy202, yymsp[0].minor.yy303);
+ yylhsminor.yy404 = sqlite3ExprFunction(pParse, 0, &yymsp[-4].minor.yy0, 0);
+ sqlite3WindowAttach(pParse, yylhsminor.yy404, yymsp[0].minor.yy49);
}
- yymsp[-4].minor.yy202 = yylhsminor.yy202;
+ yymsp[-4].minor.yy404 = yylhsminor.yy404;
break;
- case 183: /* term ::= CTIME_KW */
+ case 190: /* term ::= CTIME_KW */
{
- yylhsminor.yy202 = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0, 0);
+ yylhsminor.yy404 = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0, 0);
}
- yymsp[0].minor.yy202 = yylhsminor.yy202;
+ yymsp[0].minor.yy404 = yylhsminor.yy404;
break;
- case 184: /* expr ::= LP nexprlist COMMA expr RP */
+ case 191: /* expr ::= LP nexprlist COMMA expr RP */
{
- ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy242, yymsp[-1].minor.yy202);
- yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_VECTOR, 0, 0);
- if( yymsp[-4].minor.yy202 ){
- yymsp[-4].minor.yy202->x.pList = pList;
+ ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy70, yymsp[-1].minor.yy404);
+ yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_VECTOR, 0, 0);
+ if( yymsp[-4].minor.yy404 ){
+ yymsp[-4].minor.yy404->x.pList = pList;
if( ALWAYS(pList->nExpr) ){
- yymsp[-4].minor.yy202->flags |= pList->a[0].pExpr->flags & EP_Propagate;
+ yymsp[-4].minor.yy404->flags |= pList->a[0].pExpr->flags & EP_Propagate;
}
}else{
sqlite3ExprListDelete(pParse->db, pList);
}
}
break;
- case 185: /* expr ::= expr AND expr */
-{yymsp[-2].minor.yy202=sqlite3ExprAnd(pParse,yymsp[-2].minor.yy202,yymsp[0].minor.yy202);}
+ case 192: /* expr ::= expr AND expr */
+{yymsp[-2].minor.yy404=sqlite3ExprAnd(pParse,yymsp[-2].minor.yy404,yymsp[0].minor.yy404);}
break;
- case 186: /* expr ::= expr OR expr */
- case 187: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==187);
- case 188: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==188);
- case 189: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==189);
- case 190: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==190);
- case 191: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==191);
- case 192: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==192);
-{yymsp[-2].minor.yy202=sqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy202,yymsp[0].minor.yy202);}
+ case 193: /* expr ::= expr OR expr */
+ case 194: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==194);
+ case 195: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==195);
+ case 196: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==196);
+ case 197: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==197);
+ case 198: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==198);
+ case 199: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==199);
+{yymsp[-2].minor.yy404=sqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy404,yymsp[0].minor.yy404);}
break;
- case 193: /* likeop ::= NOT LIKE_KW|MATCH */
+ case 200: /* likeop ::= NOT LIKE_KW|MATCH */
{yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.n|=0x80000000; /*yymsp[-1].minor.yy0-overwrite-yymsp[0].minor.yy0*/}
break;
- case 194: /* expr ::= expr likeop expr */
+ case 201: /* expr ::= expr likeop expr */
{
ExprList *pList;
int bNot = yymsp[-1].minor.yy0.n & 0x80000000;
yymsp[-1].minor.yy0.n &= 0x7fffffff;
- pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy202);
- pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy202);
- yymsp[-2].minor.yy202 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0);
- if( bNot ) yymsp[-2].minor.yy202 = sqlite3PExpr(pParse, TK_NOT, yymsp[-2].minor.yy202, 0);
- if( yymsp[-2].minor.yy202 ) yymsp[-2].minor.yy202->flags |= EP_InfixFunc;
+ pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy404);
+ pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy404);
+ yymsp[-2].minor.yy404 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0);
+ if( bNot ) yymsp[-2].minor.yy404 = sqlite3PExpr(pParse, TK_NOT, yymsp[-2].minor.yy404, 0);
+ if( yymsp[-2].minor.yy404 ) yymsp[-2].minor.yy404->flags |= EP_InfixFunc;
}
break;
- case 195: /* expr ::= expr likeop expr ESCAPE expr */
+ case 202: /* expr ::= expr likeop expr ESCAPE expr */
{
ExprList *pList;
int bNot = yymsp[-3].minor.yy0.n & 0x80000000;
yymsp[-3].minor.yy0.n &= 0x7fffffff;
- pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy202);
- pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy202);
- pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy202);
- yymsp[-4].minor.yy202 = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0, 0);
- if( bNot ) yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
- if( yymsp[-4].minor.yy202 ) yymsp[-4].minor.yy202->flags |= EP_InfixFunc;
+ pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy404);
+ pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy404);
+ pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy404);
+ yymsp[-4].minor.yy404 = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0, 0);
+ if( bNot ) yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy404, 0);
+ if( yymsp[-4].minor.yy404 ) yymsp[-4].minor.yy404->flags |= EP_InfixFunc;
}
break;
- case 196: /* expr ::= expr ISNULL|NOTNULL */
-{yymsp[-1].minor.yy202 = sqlite3PExpr(pParse,yymsp[0].major,yymsp[-1].minor.yy202,0);}
+ case 203: /* expr ::= expr ISNULL|NOTNULL */
+{yymsp[-1].minor.yy404 = sqlite3PExpr(pParse,yymsp[0].major,yymsp[-1].minor.yy404,0);}
break;
- case 197: /* expr ::= expr NOT NULL */
-{yymsp[-2].minor.yy202 = sqlite3PExpr(pParse,TK_NOTNULL,yymsp[-2].minor.yy202,0);}
+ case 204: /* expr ::= expr NOT NULL */
+{yymsp[-2].minor.yy404 = sqlite3PExpr(pParse,TK_NOTNULL,yymsp[-2].minor.yy404,0);}
break;
- case 198: /* expr ::= expr IS expr */
+ case 205: /* expr ::= expr IS expr */
{
- yymsp[-2].minor.yy202 = sqlite3PExpr(pParse,TK_IS,yymsp[-2].minor.yy202,yymsp[0].minor.yy202);
- binaryToUnaryIfNull(pParse, yymsp[0].minor.yy202, yymsp[-2].minor.yy202, TK_ISNULL);
+ yymsp[-2].minor.yy404 = sqlite3PExpr(pParse,TK_IS,yymsp[-2].minor.yy404,yymsp[0].minor.yy404);
+ binaryToUnaryIfNull(pParse, yymsp[0].minor.yy404, yymsp[-2].minor.yy404, TK_ISNULL);
}
break;
- case 199: /* expr ::= expr IS NOT expr */
+ case 206: /* expr ::= expr IS NOT expr */
{
- yymsp[-3].minor.yy202 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-3].minor.yy202,yymsp[0].minor.yy202);
- binaryToUnaryIfNull(pParse, yymsp[0].minor.yy202, yymsp[-3].minor.yy202, TK_NOTNULL);
+ yymsp[-3].minor.yy404 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-3].minor.yy404,yymsp[0].minor.yy404);
+ binaryToUnaryIfNull(pParse, yymsp[0].minor.yy404, yymsp[-3].minor.yy404, TK_NOTNULL);
}
break;
- case 200: /* expr ::= NOT expr */
- case 201: /* expr ::= BITNOT expr */ yytestcase(yyruleno==201);
-{yymsp[-1].minor.yy202 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy202, 0);/*A-overwrites-B*/}
+ case 207: /* expr ::= NOT expr */
+ case 208: /* expr ::= BITNOT expr */ yytestcase(yyruleno==208);
+{yymsp[-1].minor.yy404 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy404, 0);/*A-overwrites-B*/}
break;
- case 202: /* expr ::= PLUS|MINUS expr */
+ case 209: /* expr ::= PLUS|MINUS expr */
{
- yymsp[-1].minor.yy202 = sqlite3PExpr(pParse, yymsp[-1].major==TK_PLUS ? TK_UPLUS : TK_UMINUS, yymsp[0].minor.yy202, 0);
+ yymsp[-1].minor.yy404 = sqlite3PExpr(pParse, yymsp[-1].major==TK_PLUS ? TK_UPLUS : TK_UMINUS, yymsp[0].minor.yy404, 0);
/*A-overwrites-B*/
}
break;
- case 203: /* between_op ::= BETWEEN */
- case 206: /* in_op ::= IN */ yytestcase(yyruleno==206);
-{yymsp[0].minor.yy192 = 0;}
+ case 210: /* between_op ::= BETWEEN */
+ case 213: /* in_op ::= IN */ yytestcase(yyruleno==213);
+{yymsp[0].minor.yy376 = 0;}
break;
- case 205: /* expr ::= expr between_op expr AND expr */
+ case 212: /* expr ::= expr between_op expr AND expr */
{
- ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy202);
- pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy202);
- yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy202, 0);
- if( yymsp[-4].minor.yy202 ){
- yymsp[-4].minor.yy202->x.pList = pList;
+ ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy404);
+ pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy404);
+ yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy404, 0);
+ if( yymsp[-4].minor.yy404 ){
+ yymsp[-4].minor.yy404->x.pList = pList;
}else{
sqlite3ExprListDelete(pParse->db, pList);
}
- if( yymsp[-3].minor.yy192 ) yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
+ if( yymsp[-3].minor.yy376 ) yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy404, 0);
}
break;
- case 208: /* expr ::= expr in_op LP exprlist RP */
+ case 215: /* expr ::= expr in_op LP exprlist RP */
{
- if( yymsp[-1].minor.yy242==0 ){
+ if( yymsp[-1].minor.yy70==0 ){
/* Expressions of the form
**
** expr1 IN ()
@@ -158423,197 +162431,197 @@ static YYACTIONTYPE yy_reduce(
** simplify to constants 0 (false) and 1 (true), respectively,
** regardless of the value of expr1.
*/
- sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy202);
- yymsp[-4].minor.yy202 = sqlite3Expr(pParse->db, TK_INTEGER, yymsp[-3].minor.yy192 ? "1" : "0");
- }else if( yymsp[-1].minor.yy242->nExpr==1 && sqlite3ExprIsConstant(yymsp[-1].minor.yy242->a[0].pExpr) ){
- Expr *pRHS = yymsp[-1].minor.yy242->a[0].pExpr;
- yymsp[-1].minor.yy242->a[0].pExpr = 0;
- sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy242);
+ sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy404);
+ yymsp[-4].minor.yy404 = sqlite3Expr(pParse->db, TK_INTEGER, yymsp[-3].minor.yy376 ? "1" : "0");
+ }else if( yymsp[-1].minor.yy70->nExpr==1 && sqlite3ExprIsConstant(yymsp[-1].minor.yy70->a[0].pExpr) ){
+ Expr *pRHS = yymsp[-1].minor.yy70->a[0].pExpr;
+ yymsp[-1].minor.yy70->a[0].pExpr = 0;
+ sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy70);
pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0);
- yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_EQ, yymsp[-4].minor.yy202, pRHS);
- if( yymsp[-3].minor.yy192 ) yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
+ yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_EQ, yymsp[-4].minor.yy404, pRHS);
+ if( yymsp[-3].minor.yy376 ) yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy404, 0);
}else{
- yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy202, 0);
- if( yymsp[-4].minor.yy202 ){
- yymsp[-4].minor.yy202->x.pList = yymsp[-1].minor.yy242;
- sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy202);
+ yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy404, 0);
+ if( yymsp[-4].minor.yy404 ){
+ yymsp[-4].minor.yy404->x.pList = yymsp[-1].minor.yy70;
+ sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy404);
}else{
- sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy242);
+ sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy70);
}
- if( yymsp[-3].minor.yy192 ) yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
+ if( yymsp[-3].minor.yy376 ) yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy404, 0);
}
}
break;
- case 209: /* expr ::= LP select RP */
+ case 216: /* expr ::= LP select RP */
{
- yymsp[-2].minor.yy202 = sqlite3PExpr(pParse, TK_SELECT, 0, 0);
- sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy202, yymsp[-1].minor.yy539);
+ yymsp[-2].minor.yy404 = sqlite3PExpr(pParse, TK_SELECT, 0, 0);
+ sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy404, yymsp[-1].minor.yy81);
}
break;
- case 210: /* expr ::= expr in_op LP select RP */
+ case 217: /* expr ::= expr in_op LP select RP */
{
- yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy202, 0);
- sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy202, yymsp[-1].minor.yy539);
- if( yymsp[-3].minor.yy192 ) yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
+ yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy404, 0);
+ sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy404, yymsp[-1].minor.yy81);
+ if( yymsp[-3].minor.yy376 ) yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy404, 0);
}
break;
- case 211: /* expr ::= expr in_op nm dbnm paren_exprlist */
+ case 218: /* expr ::= expr in_op nm dbnm paren_exprlist */
{
SrcList *pSrc = sqlite3SrcListAppend(pParse, 0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);
Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0);
- if( yymsp[0].minor.yy242 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy242);
- yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy202, 0);
- sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy202, pSelect);
- if( yymsp[-3].minor.yy192 ) yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
+ if( yymsp[0].minor.yy70 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy70);
+ yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy404, 0);
+ sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy404, pSelect);
+ if( yymsp[-3].minor.yy376 ) yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy404, 0);
}
break;
- case 212: /* expr ::= EXISTS LP select RP */
+ case 219: /* expr ::= EXISTS LP select RP */
{
Expr *p;
- p = yymsp[-3].minor.yy202 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0);
- sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy539);
+ p = yymsp[-3].minor.yy404 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0);
+ sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy81);
}
break;
- case 213: /* expr ::= CASE case_operand case_exprlist case_else END */
+ case 220: /* expr ::= CASE case_operand case_exprlist case_else END */
{
- yymsp[-4].minor.yy202 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy202, 0);
- if( yymsp[-4].minor.yy202 ){
- yymsp[-4].minor.yy202->x.pList = yymsp[-1].minor.yy202 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy242,yymsp[-1].minor.yy202) : yymsp[-2].minor.yy242;
- sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy202);
+ yymsp[-4].minor.yy404 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy404, 0);
+ if( yymsp[-4].minor.yy404 ){
+ yymsp[-4].minor.yy404->x.pList = yymsp[-1].minor.yy404 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy70,yymsp[-1].minor.yy404) : yymsp[-2].minor.yy70;
+ sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy404);
}else{
- sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy242);
- sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy202);
+ sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy70);
+ sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy404);
}
}
break;
- case 214: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */
+ case 221: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */
{
- yymsp[-4].minor.yy242 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy242, yymsp[-2].minor.yy202);
- yymsp[-4].minor.yy242 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy242, yymsp[0].minor.yy202);
+ yymsp[-4].minor.yy70 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy70, yymsp[-2].minor.yy404);
+ yymsp[-4].minor.yy70 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy70, yymsp[0].minor.yy404);
}
break;
- case 215: /* case_exprlist ::= WHEN expr THEN expr */
+ case 222: /* case_exprlist ::= WHEN expr THEN expr */
{
- yymsp[-3].minor.yy242 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy202);
- yymsp[-3].minor.yy242 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy242, yymsp[0].minor.yy202);
+ yymsp[-3].minor.yy70 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy404);
+ yymsp[-3].minor.yy70 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy70, yymsp[0].minor.yy404);
}
break;
- case 218: /* case_operand ::= expr */
-{yymsp[0].minor.yy202 = yymsp[0].minor.yy202; /*A-overwrites-X*/}
+ case 225: /* case_operand ::= expr */
+{yymsp[0].minor.yy404 = yymsp[0].minor.yy404; /*A-overwrites-X*/}
break;
- case 221: /* nexprlist ::= nexprlist COMMA expr */
-{yymsp[-2].minor.yy242 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy242,yymsp[0].minor.yy202);}
+ case 228: /* nexprlist ::= nexprlist COMMA expr */
+{yymsp[-2].minor.yy70 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy70,yymsp[0].minor.yy404);}
break;
- case 222: /* nexprlist ::= expr */
-{yymsp[0].minor.yy242 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy202); /*A-overwrites-Y*/}
+ case 229: /* nexprlist ::= expr */
+{yymsp[0].minor.yy70 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy404); /*A-overwrites-Y*/}
break;
- case 224: /* paren_exprlist ::= LP exprlist RP */
- case 229: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==229);
-{yymsp[-2].minor.yy242 = yymsp[-1].minor.yy242;}
+ case 231: /* paren_exprlist ::= LP exprlist RP */
+ case 236: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==236);
+{yymsp[-2].minor.yy70 = yymsp[-1].minor.yy70;}
break;
- case 225: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
+ case 232: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
{
sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0,
- sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy242, yymsp[-10].minor.yy192,
- &yymsp[-11].minor.yy0, yymsp[0].minor.yy202, SQLITE_SO_ASC, yymsp[-8].minor.yy192, SQLITE_IDXTYPE_APPDEF);
+ sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy70, yymsp[-10].minor.yy376,
+ &yymsp[-11].minor.yy0, yymsp[0].minor.yy404, SQLITE_SO_ASC, yymsp[-8].minor.yy376, SQLITE_IDXTYPE_APPDEF);
if( IN_RENAME_OBJECT && pParse->pNewIndex ){
sqlite3RenameTokenMap(pParse, pParse->pNewIndex->zName, &yymsp[-4].minor.yy0);
}
}
break;
- case 226: /* uniqueflag ::= UNIQUE */
- case 268: /* raisetype ::= ABORT */ yytestcase(yyruleno==268);
-{yymsp[0].minor.yy192 = OE_Abort;}
+ case 233: /* uniqueflag ::= UNIQUE */
+ case 275: /* raisetype ::= ABORT */ yytestcase(yyruleno==275);
+{yymsp[0].minor.yy376 = OE_Abort;}
break;
- case 227: /* uniqueflag ::= */
-{yymsp[1].minor.yy192 = OE_None;}
+ case 234: /* uniqueflag ::= */
+{yymsp[1].minor.yy376 = OE_None;}
break;
- case 230: /* eidlist ::= eidlist COMMA nm collate sortorder */
+ case 237: /* eidlist ::= eidlist COMMA nm collate sortorder */
{
- yymsp[-4].minor.yy242 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy242, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy192, yymsp[0].minor.yy192);
+ yymsp[-4].minor.yy70 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy70, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy376, yymsp[0].minor.yy376);
}
break;
- case 231: /* eidlist ::= nm collate sortorder */
+ case 238: /* eidlist ::= nm collate sortorder */
{
- yymsp[-2].minor.yy242 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy192, yymsp[0].minor.yy192); /*A-overwrites-Y*/
+ yymsp[-2].minor.yy70 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy376, yymsp[0].minor.yy376); /*A-overwrites-Y*/
}
break;
- case 234: /* cmd ::= DROP INDEX ifexists fullname */
-{sqlite3DropIndex(pParse, yymsp[0].minor.yy47, yymsp[-1].minor.yy192);}
+ case 241: /* cmd ::= DROP INDEX ifexists fullname */
+{sqlite3DropIndex(pParse, yymsp[0].minor.yy153, yymsp[-1].minor.yy376);}
break;
- case 235: /* cmd ::= VACUUM vinto */
-{sqlite3Vacuum(pParse,0,yymsp[0].minor.yy202);}
+ case 242: /* cmd ::= VACUUM vinto */
+{sqlite3Vacuum(pParse,0,yymsp[0].minor.yy404);}
break;
- case 236: /* cmd ::= VACUUM nm vinto */
-{sqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy202);}
+ case 243: /* cmd ::= VACUUM nm vinto */
+{sqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy404);}
break;
- case 239: /* cmd ::= PRAGMA nm dbnm */
+ case 246: /* cmd ::= PRAGMA nm dbnm */
{sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);}
break;
- case 240: /* cmd ::= PRAGMA nm dbnm EQ nmnum */
+ case 247: /* cmd ::= PRAGMA nm dbnm EQ nmnum */
{sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);}
break;
- case 241: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */
+ case 248: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */
{sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);}
break;
- case 242: /* cmd ::= PRAGMA nm dbnm EQ minus_num */
+ case 249: /* cmd ::= PRAGMA nm dbnm EQ minus_num */
{sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);}
break;
- case 243: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */
+ case 250: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */
{sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);}
break;
- case 246: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
+ case 253: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
{
Token all;
all.z = yymsp[-3].minor.yy0.z;
all.n = (int)(yymsp[0].minor.yy0.z - yymsp[-3].minor.yy0.z) + yymsp[0].minor.yy0.n;
- sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy447, &all);
+ sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy157, &all);
}
break;
- case 247: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
+ case 254: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
{
- sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy192, yymsp[-4].minor.yy230.a, yymsp[-4].minor.yy230.b, yymsp[-2].minor.yy47, yymsp[0].minor.yy202, yymsp[-10].minor.yy192, yymsp[-8].minor.yy192);
+ sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy376, yymsp[-4].minor.yy262.a, yymsp[-4].minor.yy262.b, yymsp[-2].minor.yy153, yymsp[0].minor.yy404, yymsp[-10].minor.yy376, yymsp[-8].minor.yy376);
yymsp[-10].minor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); /*A-overwrites-T*/
}
break;
- case 248: /* trigger_time ::= BEFORE|AFTER */
-{ yymsp[0].minor.yy192 = yymsp[0].major; /*A-overwrites-X*/ }
+ case 255: /* trigger_time ::= BEFORE|AFTER */
+{ yymsp[0].minor.yy376 = yymsp[0].major; /*A-overwrites-X*/ }
break;
- case 249: /* trigger_time ::= INSTEAD OF */
-{ yymsp[-1].minor.yy192 = TK_INSTEAD;}
+ case 256: /* trigger_time ::= INSTEAD OF */
+{ yymsp[-1].minor.yy376 = TK_INSTEAD;}
break;
- case 250: /* trigger_time ::= */
-{ yymsp[1].minor.yy192 = TK_BEFORE; }
+ case 257: /* trigger_time ::= */
+{ yymsp[1].minor.yy376 = TK_BEFORE; }
break;
- case 251: /* trigger_event ::= DELETE|INSERT */
- case 252: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==252);
-{yymsp[0].minor.yy230.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy230.b = 0;}
+ case 258: /* trigger_event ::= DELETE|INSERT */
+ case 259: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==259);
+{yymsp[0].minor.yy262.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy262.b = 0;}
break;
- case 253: /* trigger_event ::= UPDATE OF idlist */
-{yymsp[-2].minor.yy230.a = TK_UPDATE; yymsp[-2].minor.yy230.b = yymsp[0].minor.yy600;}
+ case 260: /* trigger_event ::= UPDATE OF idlist */
+{yymsp[-2].minor.yy262.a = TK_UPDATE; yymsp[-2].minor.yy262.b = yymsp[0].minor.yy436;}
break;
- case 254: /* when_clause ::= */
- case 273: /* key_opt ::= */ yytestcase(yyruleno==273);
-{ yymsp[1].minor.yy202 = 0; }
+ case 261: /* when_clause ::= */
+ case 280: /* key_opt ::= */ yytestcase(yyruleno==280);
+{ yymsp[1].minor.yy404 = 0; }
break;
- case 255: /* when_clause ::= WHEN expr */
- case 274: /* key_opt ::= KEY expr */ yytestcase(yyruleno==274);
-{ yymsp[-1].minor.yy202 = yymsp[0].minor.yy202; }
+ case 262: /* when_clause ::= WHEN expr */
+ case 281: /* key_opt ::= KEY expr */ yytestcase(yyruleno==281);
+{ yymsp[-1].minor.yy404 = yymsp[0].minor.yy404; }
break;
- case 256: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
+ case 263: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
{
- assert( yymsp[-2].minor.yy447!=0 );
- yymsp[-2].minor.yy447->pLast->pNext = yymsp[-1].minor.yy447;
- yymsp[-2].minor.yy447->pLast = yymsp[-1].minor.yy447;
+ assert( yymsp[-2].minor.yy157!=0 );
+ yymsp[-2].minor.yy157->pLast->pNext = yymsp[-1].minor.yy157;
+ yymsp[-2].minor.yy157->pLast = yymsp[-1].minor.yy157;
}
break;
- case 257: /* trigger_cmd_list ::= trigger_cmd SEMI */
+ case 264: /* trigger_cmd_list ::= trigger_cmd SEMI */
{
- assert( yymsp[-1].minor.yy447!=0 );
- yymsp[-1].minor.yy447->pLast = yymsp[-1].minor.yy447;
+ assert( yymsp[-1].minor.yy157!=0 );
+ yymsp[-1].minor.yy157->pLast = yymsp[-1].minor.yy157;
}
break;
- case 258: /* trnm ::= nm DOT nm */
+ case 265: /* trnm ::= nm DOT nm */
{
yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;
sqlite3ErrorMsg(pParse,
@@ -158621,344 +162629,368 @@ static YYACTIONTYPE yy_reduce(
"statements within triggers");
}
break;
- case 259: /* tridxby ::= INDEXED BY nm */
+ case 266: /* tridxby ::= INDEXED BY nm */
{
sqlite3ErrorMsg(pParse,
"the INDEXED BY clause is not allowed on UPDATE or DELETE statements "
"within triggers");
}
break;
- case 260: /* tridxby ::= NOT INDEXED */
+ case 267: /* tridxby ::= NOT INDEXED */
{
sqlite3ErrorMsg(pParse,
"the NOT INDEXED clause is not allowed on UPDATE or DELETE statements "
"within triggers");
}
break;
- case 261: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
-{yylhsminor.yy447 = sqlite3TriggerUpdateStep(pParse, &yymsp[-6].minor.yy0, yymsp[-2].minor.yy47, yymsp[-3].minor.yy242, yymsp[-1].minor.yy202, yymsp[-7].minor.yy192, yymsp[-8].minor.yy0.z, yymsp[0].minor.yy436);}
- yymsp[-8].minor.yy447 = yylhsminor.yy447;
+ case 268: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */
+{yylhsminor.yy157 = sqlite3TriggerUpdateStep(pParse, &yymsp[-6].minor.yy0, yymsp[-2].minor.yy153, yymsp[-3].minor.yy70, yymsp[-1].minor.yy404, yymsp[-7].minor.yy376, yymsp[-8].minor.yy0.z, yymsp[0].minor.yy504);}
+ yymsp[-8].minor.yy157 = yylhsminor.yy157;
break;
- case 262: /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
+ case 269: /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
{
- yylhsminor.yy447 = sqlite3TriggerInsertStep(pParse,&yymsp[-4].minor.yy0,yymsp[-3].minor.yy600,yymsp[-2].minor.yy539,yymsp[-6].minor.yy192,yymsp[-1].minor.yy318,yymsp[-7].minor.yy436,yymsp[0].minor.yy436);/*yylhsminor.yy447-overwrites-yymsp[-6].minor.yy192*/
+ yylhsminor.yy157 = sqlite3TriggerInsertStep(pParse,&yymsp[-4].minor.yy0,yymsp[-3].minor.yy436,yymsp[-2].minor.yy81,yymsp[-6].minor.yy376,yymsp[-1].minor.yy190,yymsp[-7].minor.yy504,yymsp[0].minor.yy504);/*yylhsminor.yy157-overwrites-yymsp[-6].minor.yy376*/
}
- yymsp[-7].minor.yy447 = yylhsminor.yy447;
+ yymsp[-7].minor.yy157 = yylhsminor.yy157;
break;
- case 263: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
-{yylhsminor.yy447 = sqlite3TriggerDeleteStep(pParse, &yymsp[-3].minor.yy0, yymsp[-1].minor.yy202, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy436);}
- yymsp[-5].minor.yy447 = yylhsminor.yy447;
+ case 270: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
+{yylhsminor.yy157 = sqlite3TriggerDeleteStep(pParse, &yymsp[-3].minor.yy0, yymsp[-1].minor.yy404, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy504);}
+ yymsp[-5].minor.yy157 = yylhsminor.yy157;
break;
- case 264: /* trigger_cmd ::= scanpt select scanpt */
-{yylhsminor.yy447 = sqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy539, yymsp[-2].minor.yy436, yymsp[0].minor.yy436); /*yylhsminor.yy447-overwrites-yymsp[-1].minor.yy539*/}
- yymsp[-2].minor.yy447 = yylhsminor.yy447;
+ case 271: /* trigger_cmd ::= scanpt select scanpt */
+{yylhsminor.yy157 = sqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy81, yymsp[-2].minor.yy504, yymsp[0].minor.yy504); /*yylhsminor.yy157-overwrites-yymsp[-1].minor.yy81*/}
+ yymsp[-2].minor.yy157 = yylhsminor.yy157;
break;
- case 265: /* expr ::= RAISE LP IGNORE RP */
+ case 272: /* expr ::= RAISE LP IGNORE RP */
{
- yymsp[-3].minor.yy202 = sqlite3PExpr(pParse, TK_RAISE, 0, 0);
- if( yymsp[-3].minor.yy202 ){
- yymsp[-3].minor.yy202->affExpr = OE_Ignore;
+ yymsp[-3].minor.yy404 = sqlite3PExpr(pParse, TK_RAISE, 0, 0);
+ if( yymsp[-3].minor.yy404 ){
+ yymsp[-3].minor.yy404->affExpr = OE_Ignore;
}
}
break;
- case 266: /* expr ::= RAISE LP raisetype COMMA nm RP */
+ case 273: /* expr ::= RAISE LP raisetype COMMA nm RP */
{
- yymsp[-5].minor.yy202 = sqlite3ExprAlloc(pParse->db, TK_RAISE, &yymsp[-1].minor.yy0, 1);
- if( yymsp[-5].minor.yy202 ) {
- yymsp[-5].minor.yy202->affExpr = (char)yymsp[-3].minor.yy192;
+ yymsp[-5].minor.yy404 = sqlite3ExprAlloc(pParse->db, TK_RAISE, &yymsp[-1].minor.yy0, 1);
+ if( yymsp[-5].minor.yy404 ) {
+ yymsp[-5].minor.yy404->affExpr = (char)yymsp[-3].minor.yy376;
}
}
break;
- case 267: /* raisetype ::= ROLLBACK */
-{yymsp[0].minor.yy192 = OE_Rollback;}
+ case 274: /* raisetype ::= ROLLBACK */
+{yymsp[0].minor.yy376 = OE_Rollback;}
break;
- case 269: /* raisetype ::= FAIL */
-{yymsp[0].minor.yy192 = OE_Fail;}
+ case 276: /* raisetype ::= FAIL */
+{yymsp[0].minor.yy376 = OE_Fail;}
break;
- case 270: /* cmd ::= DROP TRIGGER ifexists fullname */
+ case 277: /* cmd ::= DROP TRIGGER ifexists fullname */
{
- sqlite3DropTrigger(pParse,yymsp[0].minor.yy47,yymsp[-1].minor.yy192);
+ sqlite3DropTrigger(pParse,yymsp[0].minor.yy153,yymsp[-1].minor.yy376);
}
break;
- case 271: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
+ case 278: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
{
- sqlite3Attach(pParse, yymsp[-3].minor.yy202, yymsp[-1].minor.yy202, yymsp[0].minor.yy202);
+ sqlite3Attach(pParse, yymsp[-3].minor.yy404, yymsp[-1].minor.yy404, yymsp[0].minor.yy404);
}
break;
- case 272: /* cmd ::= DETACH database_kw_opt expr */
+ case 279: /* cmd ::= DETACH database_kw_opt expr */
{
- sqlite3Detach(pParse, yymsp[0].minor.yy202);
+ sqlite3Detach(pParse, yymsp[0].minor.yy404);
}
break;
- case 275: /* cmd ::= REINDEX */
+ case 282: /* cmd ::= REINDEX */
{sqlite3Reindex(pParse, 0, 0);}
break;
- case 276: /* cmd ::= REINDEX nm dbnm */
+ case 283: /* cmd ::= REINDEX nm dbnm */
{sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);}
break;
- case 277: /* cmd ::= ANALYZE */
+ case 284: /* cmd ::= ANALYZE */
{sqlite3Analyze(pParse, 0, 0);}
break;
- case 278: /* cmd ::= ANALYZE nm dbnm */
+ case 285: /* cmd ::= ANALYZE nm dbnm */
{sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);}
break;
- case 279: /* cmd ::= ALTER TABLE fullname RENAME TO nm */
+ case 286: /* cmd ::= ALTER TABLE fullname RENAME TO nm */
{
- sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy47,&yymsp[0].minor.yy0);
+ sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy153,&yymsp[0].minor.yy0);
}
break;
- case 280: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
+ case 287: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
{
yymsp[-1].minor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-1].minor.yy0.z) + pParse->sLastToken.n;
sqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0);
}
break;
- case 281: /* add_column_fullname ::= fullname */
+ case 288: /* cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */
+{
+ sqlite3AlterDropColumn(pParse, yymsp[-3].minor.yy153, &yymsp[0].minor.yy0);
+}
+ break;
+ case 289: /* add_column_fullname ::= fullname */
{
disableLookaside(pParse);
- sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy47);
+ sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy153);
}
break;
- case 282: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
+ case 290: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
{
- sqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy47, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0);
+ sqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy153, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0);
}
break;
- case 283: /* cmd ::= create_vtab */
+ case 291: /* cmd ::= create_vtab */
{sqlite3VtabFinishParse(pParse,0);}
break;
- case 284: /* cmd ::= create_vtab LP vtabarglist RP */
+ case 292: /* cmd ::= create_vtab LP vtabarglist RP */
{sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);}
break;
- case 285: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
+ case 293: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
{
- sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy192);
+ sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy376);
}
break;
- case 286: /* vtabarg ::= */
+ case 294: /* vtabarg ::= */
{sqlite3VtabArgInit(pParse);}
break;
- case 287: /* vtabargtoken ::= ANY */
- case 288: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==288);
- case 289: /* lp ::= LP */ yytestcase(yyruleno==289);
+ case 295: /* vtabargtoken ::= ANY */
+ case 296: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==296);
+ case 297: /* lp ::= LP */ yytestcase(yyruleno==297);
{sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);}
break;
- case 290: /* with ::= WITH wqlist */
- case 291: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==291);
-{ sqlite3WithPush(pParse, yymsp[0].minor.yy131, 1); }
+ case 298: /* with ::= WITH wqlist */
+ case 299: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==299);
+{ sqlite3WithPush(pParse, yymsp[0].minor.yy103, 1); }
+ break;
+ case 300: /* wqas ::= AS */
+{yymsp[0].minor.yy552 = M10d_Any;}
+ break;
+ case 301: /* wqas ::= AS MATERIALIZED */
+{yymsp[-1].minor.yy552 = M10d_Yes;}
+ break;
+ case 302: /* wqas ::= AS NOT MATERIALIZED */
+{yymsp[-2].minor.yy552 = M10d_No;}
break;
- case 292: /* wqlist ::= nm eidlist_opt AS LP select RP */
+ case 303: /* wqitem ::= nm eidlist_opt wqas LP select RP */
{
- yymsp[-5].minor.yy131 = sqlite3WithAdd(pParse, 0, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy242, yymsp[-1].minor.yy539); /*A-overwrites-X*/
+ yymsp[-5].minor.yy329 = sqlite3CteNew(pParse, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy70, yymsp[-1].minor.yy81, yymsp[-3].minor.yy552); /*A-overwrites-X*/
}
break;
- case 293: /* wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */
+ case 304: /* wqlist ::= wqitem */
{
- yymsp[-7].minor.yy131 = sqlite3WithAdd(pParse, yymsp[-7].minor.yy131, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy242, yymsp[-1].minor.yy539);
+ yymsp[0].minor.yy103 = sqlite3WithAdd(pParse, 0, yymsp[0].minor.yy329); /*A-overwrites-X*/
}
break;
- case 294: /* windowdefn_list ::= windowdefn */
-{ yylhsminor.yy303 = yymsp[0].minor.yy303; }
- yymsp[0].minor.yy303 = yylhsminor.yy303;
+ case 305: /* wqlist ::= wqlist COMMA wqitem */
+{
+ yymsp[-2].minor.yy103 = sqlite3WithAdd(pParse, yymsp[-2].minor.yy103, yymsp[0].minor.yy329);
+}
break;
- case 295: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */
+ case 306: /* windowdefn_list ::= windowdefn */
+{ yylhsminor.yy49 = yymsp[0].minor.yy49; }
+ yymsp[0].minor.yy49 = yylhsminor.yy49;
+ break;
+ case 307: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */
{
- assert( yymsp[0].minor.yy303!=0 );
- sqlite3WindowChain(pParse, yymsp[0].minor.yy303, yymsp[-2].minor.yy303);
- yymsp[0].minor.yy303->pNextWin = yymsp[-2].minor.yy303;
- yylhsminor.yy303 = yymsp[0].minor.yy303;
+ assert( yymsp[0].minor.yy49!=0 );
+ sqlite3WindowChain(pParse, yymsp[0].minor.yy49, yymsp[-2].minor.yy49);
+ yymsp[0].minor.yy49->pNextWin = yymsp[-2].minor.yy49;
+ yylhsminor.yy49 = yymsp[0].minor.yy49;
}
- yymsp[-2].minor.yy303 = yylhsminor.yy303;
+ yymsp[-2].minor.yy49 = yylhsminor.yy49;
break;
- case 296: /* windowdefn ::= nm AS LP window RP */
+ case 308: /* windowdefn ::= nm AS LP window RP */
{
- if( ALWAYS(yymsp[-1].minor.yy303) ){
- yymsp[-1].minor.yy303->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n);
+ if( ALWAYS(yymsp[-1].minor.yy49) ){
+ yymsp[-1].minor.yy49->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n);
}
- yylhsminor.yy303 = yymsp[-1].minor.yy303;
+ yylhsminor.yy49 = yymsp[-1].minor.yy49;
}
- yymsp[-4].minor.yy303 = yylhsminor.yy303;
+ yymsp[-4].minor.yy49 = yylhsminor.yy49;
break;
- case 297: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */
+ case 309: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */
{
- yymsp[-4].minor.yy303 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy303, yymsp[-2].minor.yy242, yymsp[-1].minor.yy242, 0);
+ yymsp[-4].minor.yy49 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy49, yymsp[-2].minor.yy70, yymsp[-1].minor.yy70, 0);
}
break;
- case 298: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
+ case 310: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
{
- yylhsminor.yy303 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy303, yymsp[-2].minor.yy242, yymsp[-1].minor.yy242, &yymsp[-5].minor.yy0);
+ yylhsminor.yy49 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy49, yymsp[-2].minor.yy70, yymsp[-1].minor.yy70, &yymsp[-5].minor.yy0);
}
- yymsp[-5].minor.yy303 = yylhsminor.yy303;
+ yymsp[-5].minor.yy49 = yylhsminor.yy49;
break;
- case 299: /* window ::= ORDER BY sortlist frame_opt */
+ case 311: /* window ::= ORDER BY sortlist frame_opt */
{
- yymsp[-3].minor.yy303 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy303, 0, yymsp[-1].minor.yy242, 0);
+ yymsp[-3].minor.yy49 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy49, 0, yymsp[-1].minor.yy70, 0);
}
break;
- case 300: /* window ::= nm ORDER BY sortlist frame_opt */
+ case 312: /* window ::= nm ORDER BY sortlist frame_opt */
{
- yylhsminor.yy303 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy303, 0, yymsp[-1].minor.yy242, &yymsp[-4].minor.yy0);
+ yylhsminor.yy49 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy49, 0, yymsp[-1].minor.yy70, &yymsp[-4].minor.yy0);
}
- yymsp[-4].minor.yy303 = yylhsminor.yy303;
+ yymsp[-4].minor.yy49 = yylhsminor.yy49;
break;
- case 301: /* window ::= frame_opt */
- case 320: /* filter_over ::= over_clause */ yytestcase(yyruleno==320);
+ case 313: /* window ::= frame_opt */
+ case 332: /* filter_over ::= over_clause */ yytestcase(yyruleno==332);
{
- yylhsminor.yy303 = yymsp[0].minor.yy303;
+ yylhsminor.yy49 = yymsp[0].minor.yy49;
}
- yymsp[0].minor.yy303 = yylhsminor.yy303;
+ yymsp[0].minor.yy49 = yylhsminor.yy49;
break;
- case 302: /* window ::= nm frame_opt */
+ case 314: /* window ::= nm frame_opt */
{
- yylhsminor.yy303 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy303, 0, 0, &yymsp[-1].minor.yy0);
+ yylhsminor.yy49 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy49, 0, 0, &yymsp[-1].minor.yy0);
}
- yymsp[-1].minor.yy303 = yylhsminor.yy303;
+ yymsp[-1].minor.yy49 = yylhsminor.yy49;
break;
- case 303: /* frame_opt ::= */
+ case 315: /* frame_opt ::= */
{
- yymsp[1].minor.yy303 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0);
+ yymsp[1].minor.yy49 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0);
}
break;
- case 304: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
+ case 316: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
{
- yylhsminor.yy303 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy192, yymsp[-1].minor.yy77.eType, yymsp[-1].minor.yy77.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy58);
+ yylhsminor.yy49 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy376, yymsp[-1].minor.yy117.eType, yymsp[-1].minor.yy117.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy552);
}
- yymsp[-2].minor.yy303 = yylhsminor.yy303;
+ yymsp[-2].minor.yy49 = yylhsminor.yy49;
break;
- case 305: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
+ case 317: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
{
- yylhsminor.yy303 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy192, yymsp[-3].minor.yy77.eType, yymsp[-3].minor.yy77.pExpr, yymsp[-1].minor.yy77.eType, yymsp[-1].minor.yy77.pExpr, yymsp[0].minor.yy58);
+ yylhsminor.yy49 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy376, yymsp[-3].minor.yy117.eType, yymsp[-3].minor.yy117.pExpr, yymsp[-1].minor.yy117.eType, yymsp[-1].minor.yy117.pExpr, yymsp[0].minor.yy552);
}
- yymsp[-5].minor.yy303 = yylhsminor.yy303;
+ yymsp[-5].minor.yy49 = yylhsminor.yy49;
break;
- case 307: /* frame_bound_s ::= frame_bound */
- case 309: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==309);
-{yylhsminor.yy77 = yymsp[0].minor.yy77;}
- yymsp[0].minor.yy77 = yylhsminor.yy77;
+ case 319: /* frame_bound_s ::= frame_bound */
+ case 321: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==321);
+{yylhsminor.yy117 = yymsp[0].minor.yy117;}
+ yymsp[0].minor.yy117 = yylhsminor.yy117;
break;
- case 308: /* frame_bound_s ::= UNBOUNDED PRECEDING */
- case 310: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==310);
- case 312: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==312);
-{yylhsminor.yy77.eType = yymsp[-1].major; yylhsminor.yy77.pExpr = 0;}
- yymsp[-1].minor.yy77 = yylhsminor.yy77;
+ case 320: /* frame_bound_s ::= UNBOUNDED PRECEDING */
+ case 322: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==322);
+ case 324: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==324);
+{yylhsminor.yy117.eType = yymsp[-1].major; yylhsminor.yy117.pExpr = 0;}
+ yymsp[-1].minor.yy117 = yylhsminor.yy117;
break;
- case 311: /* frame_bound ::= expr PRECEDING|FOLLOWING */
-{yylhsminor.yy77.eType = yymsp[0].major; yylhsminor.yy77.pExpr = yymsp[-1].minor.yy202;}
- yymsp[-1].minor.yy77 = yylhsminor.yy77;
+ case 323: /* frame_bound ::= expr PRECEDING|FOLLOWING */
+{yylhsminor.yy117.eType = yymsp[0].major; yylhsminor.yy117.pExpr = yymsp[-1].minor.yy404;}
+ yymsp[-1].minor.yy117 = yylhsminor.yy117;
break;
- case 313: /* frame_exclude_opt ::= */
-{yymsp[1].minor.yy58 = 0;}
+ case 325: /* frame_exclude_opt ::= */
+{yymsp[1].minor.yy552 = 0;}
break;
- case 314: /* frame_exclude_opt ::= EXCLUDE frame_exclude */
-{yymsp[-1].minor.yy58 = yymsp[0].minor.yy58;}
+ case 326: /* frame_exclude_opt ::= EXCLUDE frame_exclude */
+{yymsp[-1].minor.yy552 = yymsp[0].minor.yy552;}
break;
- case 315: /* frame_exclude ::= NO OTHERS */
- case 316: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==316);
-{yymsp[-1].minor.yy58 = yymsp[-1].major; /*A-overwrites-X*/}
+ case 327: /* frame_exclude ::= NO OTHERS */
+ case 328: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==328);
+{yymsp[-1].minor.yy552 = yymsp[-1].major; /*A-overwrites-X*/}
break;
- case 317: /* frame_exclude ::= GROUP|TIES */
-{yymsp[0].minor.yy58 = yymsp[0].major; /*A-overwrites-X*/}
+ case 329: /* frame_exclude ::= GROUP|TIES */
+{yymsp[0].minor.yy552 = yymsp[0].major; /*A-overwrites-X*/}
break;
- case 318: /* window_clause ::= WINDOW windowdefn_list */
-{ yymsp[-1].minor.yy303 = yymsp[0].minor.yy303; }
+ case 330: /* window_clause ::= WINDOW windowdefn_list */
+{ yymsp[-1].minor.yy49 = yymsp[0].minor.yy49; }
break;
- case 319: /* filter_over ::= filter_clause over_clause */
+ case 331: /* filter_over ::= filter_clause over_clause */
{
- yymsp[0].minor.yy303->pFilter = yymsp[-1].minor.yy202;
- yylhsminor.yy303 = yymsp[0].minor.yy303;
+ if( yymsp[0].minor.yy49 ){
+ yymsp[0].minor.yy49->pFilter = yymsp[-1].minor.yy404;
+ }else{
+ sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy404);
+ }
+ yylhsminor.yy49 = yymsp[0].minor.yy49;
}
- yymsp[-1].minor.yy303 = yylhsminor.yy303;
+ yymsp[-1].minor.yy49 = yylhsminor.yy49;
break;
- case 321: /* filter_over ::= filter_clause */
+ case 333: /* filter_over ::= filter_clause */
{
- yylhsminor.yy303 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window));
- if( yylhsminor.yy303 ){
- yylhsminor.yy303->eFrmType = TK_FILTER;
- yylhsminor.yy303->pFilter = yymsp[0].minor.yy202;
+ yylhsminor.yy49 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window));
+ if( yylhsminor.yy49 ){
+ yylhsminor.yy49->eFrmType = TK_FILTER;
+ yylhsminor.yy49->pFilter = yymsp[0].minor.yy404;
}else{
- sqlite3ExprDelete(pParse->db, yymsp[0].minor.yy202);
+ sqlite3ExprDelete(pParse->db, yymsp[0].minor.yy404);
}
}
- yymsp[0].minor.yy303 = yylhsminor.yy303;
+ yymsp[0].minor.yy49 = yylhsminor.yy49;
break;
- case 322: /* over_clause ::= OVER LP window RP */
+ case 334: /* over_clause ::= OVER LP window RP */
{
- yymsp[-3].minor.yy303 = yymsp[-1].minor.yy303;
- assert( yymsp[-3].minor.yy303!=0 );
+ yymsp[-3].minor.yy49 = yymsp[-1].minor.yy49;
+ assert( yymsp[-3].minor.yy49!=0 );
}
break;
- case 323: /* over_clause ::= OVER nm */
+ case 335: /* over_clause ::= OVER nm */
{
- yymsp[-1].minor.yy303 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window));
- if( yymsp[-1].minor.yy303 ){
- yymsp[-1].minor.yy303->zName = sqlite3DbStrNDup(pParse->db, yymsp[0].minor.yy0.z, yymsp[0].minor.yy0.n);
+ yymsp[-1].minor.yy49 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window));
+ if( yymsp[-1].minor.yy49 ){
+ yymsp[-1].minor.yy49->zName = sqlite3DbStrNDup(pParse->db, yymsp[0].minor.yy0.z, yymsp[0].minor.yy0.n);
}
}
break;
- case 324: /* filter_clause ::= FILTER LP WHERE expr RP */
-{ yymsp[-4].minor.yy202 = yymsp[-1].minor.yy202; }
+ case 336: /* filter_clause ::= FILTER LP WHERE expr RP */
+{ yymsp[-4].minor.yy404 = yymsp[-1].minor.yy404; }
break;
default:
- /* (325) input ::= cmdlist */ yytestcase(yyruleno==325);
- /* (326) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==326);
- /* (327) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=327);
- /* (328) ecmd ::= SEMI */ yytestcase(yyruleno==328);
- /* (329) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==329);
- /* (330) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=330);
- /* (331) trans_opt ::= */ yytestcase(yyruleno==331);
- /* (332) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==332);
- /* (333) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==333);
- /* (334) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==334);
- /* (335) savepoint_opt ::= */ yytestcase(yyruleno==335);
- /* (336) cmd ::= create_table create_table_args */ yytestcase(yyruleno==336);
- /* (337) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==337);
- /* (338) columnlist ::= columnname carglist */ yytestcase(yyruleno==338);
- /* (339) nm ::= ID|INDEXED */ yytestcase(yyruleno==339);
- /* (340) nm ::= STRING */ yytestcase(yyruleno==340);
- /* (341) nm ::= JOIN_KW */ yytestcase(yyruleno==341);
- /* (342) typetoken ::= typename */ yytestcase(yyruleno==342);
- /* (343) typename ::= ID|STRING */ yytestcase(yyruleno==343);
- /* (344) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=344);
- /* (345) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=345);
- /* (346) carglist ::= carglist ccons */ yytestcase(yyruleno==346);
- /* (347) carglist ::= */ yytestcase(yyruleno==347);
- /* (348) ccons ::= NULL onconf */ yytestcase(yyruleno==348);
- /* (349) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==349);
- /* (350) ccons ::= AS generated */ yytestcase(yyruleno==350);
- /* (351) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==351);
- /* (352) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==352);
- /* (353) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=353);
- /* (354) tconscomma ::= */ yytestcase(yyruleno==354);
- /* (355) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=355);
- /* (356) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=356);
- /* (357) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=357);
- /* (358) oneselect ::= values */ yytestcase(yyruleno==358);
- /* (359) sclp ::= selcollist COMMA */ yytestcase(yyruleno==359);
- /* (360) as ::= ID|STRING */ yytestcase(yyruleno==360);
- /* (361) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=361);
- /* (362) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==362);
- /* (363) exprlist ::= nexprlist */ yytestcase(yyruleno==363);
- /* (364) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=364);
- /* (365) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=365);
- /* (366) nmnum ::= ON */ yytestcase(yyruleno==366);
- /* (367) nmnum ::= DELETE */ yytestcase(yyruleno==367);
- /* (368) nmnum ::= DEFAULT */ yytestcase(yyruleno==368);
- /* (369) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==369);
- /* (370) foreach_clause ::= */ yytestcase(yyruleno==370);
- /* (371) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==371);
- /* (372) trnm ::= nm */ yytestcase(yyruleno==372);
- /* (373) tridxby ::= */ yytestcase(yyruleno==373);
- /* (374) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==374);
- /* (375) database_kw_opt ::= */ yytestcase(yyruleno==375);
- /* (376) kwcolumn_opt ::= */ yytestcase(yyruleno==376);
- /* (377) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==377);
- /* (378) vtabarglist ::= vtabarg */ yytestcase(yyruleno==378);
- /* (379) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==379);
- /* (380) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==380);
- /* (381) anylist ::= */ yytestcase(yyruleno==381);
- /* (382) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==382);
- /* (383) anylist ::= anylist ANY */ yytestcase(yyruleno==383);
- /* (384) with ::= */ yytestcase(yyruleno==384);
+ /* (337) input ::= cmdlist */ yytestcase(yyruleno==337);
+ /* (338) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==338);
+ /* (339) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=339);
+ /* (340) ecmd ::= SEMI */ yytestcase(yyruleno==340);
+ /* (341) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==341);
+ /* (342) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=342);
+ /* (343) trans_opt ::= */ yytestcase(yyruleno==343);
+ /* (344) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==344);
+ /* (345) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==345);
+ /* (346) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==346);
+ /* (347) savepoint_opt ::= */ yytestcase(yyruleno==347);
+ /* (348) cmd ::= create_table create_table_args */ yytestcase(yyruleno==348);
+ /* (349) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==349);
+ /* (350) columnlist ::= columnname carglist */ yytestcase(yyruleno==350);
+ /* (351) nm ::= ID|INDEXED */ yytestcase(yyruleno==351);
+ /* (352) nm ::= STRING */ yytestcase(yyruleno==352);
+ /* (353) nm ::= JOIN_KW */ yytestcase(yyruleno==353);
+ /* (354) typetoken ::= typename */ yytestcase(yyruleno==354);
+ /* (355) typename ::= ID|STRING */ yytestcase(yyruleno==355);
+ /* (356) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=356);
+ /* (357) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=357);
+ /* (358) carglist ::= carglist ccons */ yytestcase(yyruleno==358);
+ /* (359) carglist ::= */ yytestcase(yyruleno==359);
+ /* (360) ccons ::= NULL onconf */ yytestcase(yyruleno==360);
+ /* (361) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==361);
+ /* (362) ccons ::= AS generated */ yytestcase(yyruleno==362);
+ /* (363) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==363);
+ /* (364) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==364);
+ /* (365) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=365);
+ /* (366) tconscomma ::= */ yytestcase(yyruleno==366);
+ /* (367) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=367);
+ /* (368) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=368);
+ /* (369) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=369);
+ /* (370) oneselect ::= values */ yytestcase(yyruleno==370);
+ /* (371) sclp ::= selcollist COMMA */ yytestcase(yyruleno==371);
+ /* (372) as ::= ID|STRING */ yytestcase(yyruleno==372);
+ /* (373) returning ::= */ yytestcase(yyruleno==373);
+ /* (374) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=374);
+ /* (375) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==375);
+ /* (376) exprlist ::= nexprlist */ yytestcase(yyruleno==376);
+ /* (377) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=377);
+ /* (378) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=378);
+ /* (379) nmnum ::= ON */ yytestcase(yyruleno==379);
+ /* (380) nmnum ::= DELETE */ yytestcase(yyruleno==380);
+ /* (381) nmnum ::= DEFAULT */ yytestcase(yyruleno==381);
+ /* (382) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==382);
+ /* (383) foreach_clause ::= */ yytestcase(yyruleno==383);
+ /* (384) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==384);
+ /* (385) trnm ::= nm */ yytestcase(yyruleno==385);
+ /* (386) tridxby ::= */ yytestcase(yyruleno==386);
+ /* (387) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==387);
+ /* (388) database_kw_opt ::= */ yytestcase(yyruleno==388);
+ /* (389) kwcolumn_opt ::= */ yytestcase(yyruleno==389);
+ /* (390) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==390);
+ /* (391) vtabarglist ::= vtabarg */ yytestcase(yyruleno==391);
+ /* (392) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==392);
+ /* (393) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==393);
+ /* (394) anylist ::= */ yytestcase(yyruleno==394);
+ /* (395) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==395);
+ /* (396) anylist ::= anylist ANY */ yytestcase(yyruleno==396);
+ /* (397) with ::= */ yytestcase(yyruleno==397);
break;
/********** End reduce actions ************************************************/
};
@@ -159110,12 +163142,56 @@ SQLITE_PRIVATE void sqlite3Parser(
}
#endif
- do{
+ while(1){ /* Exit by "break" */
+ assert( yypParser->yytos>=yypParser->yystack );
assert( yyact==yypParser->yytos->stateno );
yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact);
if( yyact >= YY_MIN_REDUCE ){
- yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor,
- yyminor sqlite3ParserCTX_PARAM);
+ unsigned int yyruleno = yyact - YY_MIN_REDUCE; /* Reduce by this rule */
+ assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) );
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ int yysize = yyRuleInfoNRhs[yyruleno];
+ if( yysize ){
+ fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n",
+ yyTracePrompt,
+ yyruleno, yyRuleName[yyruleno],
+ yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action",
+ yypParser->yytos[yysize].stateno);
+ }else{
+ fprintf(yyTraceFILE, "%sReduce %d [%s]%s.\n",
+ yyTracePrompt, yyruleno, yyRuleName[yyruleno],
+ yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action");
+ }
+ }
+#endif /* NDEBUG */
+
+ /* Check that the stack is large enough to grow by a single entry
+ ** if the RHS of the rule is empty. This ensures that there is room
+ ** enough on the stack to push the LHS value */
+ if( yyRuleInfoNRhs[yyruleno]==0 ){
+#ifdef YYTRACKMAXSTACKDEPTH
+ if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){
+ yypParser->yyhwm++;
+ assert( yypParser->yyhwm ==
+ (int)(yypParser->yytos - yypParser->yystack));
+ }
+#endif
+#if YYSTACKDEPTH>0
+ if( yypParser->yytos>=yypParser->yystackEnd ){
+ yyStackOverflow(yypParser);
+ break;
+ }
+#else
+ if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){
+ if( yyGrowStack(yypParser) ){
+ yyStackOverflow(yypParser);
+ break;
+ }
+ }
+#endif
+ }
+ yyact = yy_reduce(yypParser,yyruleno,yymajor,yyminor sqlite3ParserCTX_PARAM);
}else if( yyact <= YY_MAX_SHIFTREDUCE ){
yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor);
#ifndef YYNOERRORRECOVERY
@@ -159228,7 +163304,7 @@ SQLITE_PRIVATE void sqlite3Parser(
break;
#endif
}
- }while( yypParser->yytos>yypParser->yystack );
+ }
#ifndef NDEBUG
if( yyTraceFILE ){
yyStackEntry *i;
@@ -159289,8 +163365,8 @@ SQLITE_PRIVATE int sqlite3ParserFallback(int iToken){
** all of them need to be used within the switch.
*/
#define CC_X 0 /* The letter 'x', or start of BLOB literal */
-#define CC_KYWD 1 /* Alphabetics or '_'. Usable in a keyword */
-#define CC_ID 2 /* unicode characters usable in IDs */
+#define CC_KYWD0 1 /* First letter of a keyword */
+#define CC_KYWD 2 /* Alphabetics or '_'. Usable in a keyword */
#define CC_DIGIT 3 /* Digits */
#define CC_DOLLAR 4 /* '$' */
#define CC_VARALPHA 5 /* '@', '#', ':'. Alphabetic SQL variables */
@@ -159315,47 +163391,49 @@ SQLITE_PRIVATE int sqlite3ParserFallback(int iToken){
#define CC_AND 24 /* '&' */
#define CC_TILDA 25 /* '~' */
#define CC_DOT 26 /* '.' */
-#define CC_ILLEGAL 27 /* Illegal character */
-#define CC_NUL 28 /* 0x00 */
+#define CC_ID 27 /* unicode characters usable in IDs */
+#define CC_ILLEGAL 28 /* Illegal character */
+#define CC_NUL 29 /* 0x00 */
+#define CC_BOM 30 /* First byte of UTF8 BOM: 0xEF 0xBB 0xBF */
static const unsigned char aiClass[] = {
#ifdef SQLITE_ASCII
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
-/* 0x */ 28, 27, 27, 27, 27, 27, 27, 27, 27, 7, 7, 27, 7, 7, 27, 27,
-/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* 0x */ 29, 28, 28, 28, 28, 28, 28, 28, 28, 7, 7, 28, 7, 7, 28, 28,
+/* 1x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
/* 2x */ 7, 15, 8, 5, 4, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16,
/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 19, 12, 14, 13, 6,
/* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 9, 27, 27, 27, 1,
+/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 9, 28, 28, 28, 2,
/* 6x */ 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 10, 27, 25, 27,
-/* 8x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
-/* 9x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
-/* Ax */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
-/* Bx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
-/* Cx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
-/* Dx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
-/* Ex */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
-/* Fx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
+/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 28, 10, 28, 25, 28,
+/* 8x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* 9x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Ax */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Cx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Dx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Ex */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 30,
+/* Fx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27
#endif
#ifdef SQLITE_EBCDIC
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
-/* 0x */ 27, 27, 27, 27, 27, 7, 27, 27, 27, 27, 27, 27, 7, 7, 27, 27,
-/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
-/* 2x */ 27, 27, 27, 27, 27, 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
-/* 3x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
-/* 4x */ 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 12, 17, 20, 10,
-/* 5x */ 24, 27, 27, 27, 27, 27, 27, 27, 27, 27, 15, 4, 21, 18, 19, 27,
-/* 6x */ 11, 16, 27, 27, 27, 27, 27, 27, 27, 27, 27, 23, 22, 1, 13, 6,
-/* 7x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 8, 5, 5, 5, 8, 14, 8,
-/* 8x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27,
-/* 9x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27,
-/* Ax */ 27, 25, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27,
-/* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 9, 27, 27, 27, 27, 27,
-/* Cx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27,
-/* Dx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27,
-/* Ex */ 27, 27, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27,
-/* Fx */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 27, 27, 27, 27, 27, 27,
+/* 0x */ 29, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 7, 7, 28, 28,
+/* 1x */ 28, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+/* 2x */ 28, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+/* 3x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+/* 4x */ 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 26, 12, 17, 20, 10,
+/* 5x */ 24, 28, 28, 28, 28, 28, 28, 28, 28, 28, 15, 4, 21, 18, 19, 28,
+/* 6x */ 11, 16, 28, 28, 28, 28, 28, 28, 28, 28, 28, 23, 22, 2, 13, 6,
+/* 7x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 8, 5, 5, 5, 8, 14, 8,
+/* 8x */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28,
+/* 9x */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28,
+/* Ax */ 28, 25, 1, 1, 1, 1, 1, 0, 2, 2, 28, 28, 28, 28, 28, 28,
+/* Bx */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 9, 28, 28, 28, 28, 28,
+/* Cx */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28,
+/* Dx */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28,
+/* Ex */ 28, 28, 1, 1, 1, 1, 1, 0, 2, 2, 28, 28, 28, 28, 28, 28,
+/* Fx */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 28, 28, 28, 28, 28, 28,
#endif
};
@@ -159420,20 +163498,21 @@ const unsigned char ebcdicToAscii[] = {
** is substantially reduced. This is important for embedded applications
** on platforms with limited memory.
*/
-/* Hash score: 227 */
-/* zKWText[] encodes 984 bytes of keyword text in 648 bytes */
+/* Hash score: 231 */
+/* zKWText[] encodes 1007 bytes of keyword text in 667 bytes */
/* REINDEXEDESCAPEACHECKEYBEFOREIGNOREGEXPLAINSTEADDATABASELECT */
/* ABLEFTHENDEFERRABLELSEXCLUDELETEMPORARYISNULLSAVEPOINTERSECT */
/* IESNOTNULLIKEXCEPTRANSACTIONATURALTERAISEXCLUSIVEXISTS */
/* CONSTRAINTOFFSETRIGGERANGENERATEDETACHAVINGLOBEGINNEREFERENCES */
/* UNIQUERYWITHOUTERELEASEATTACHBETWEENOTHINGROUPSCASCADEFAULT */
/* CASECOLLATECREATECURRENT_DATEIMMEDIATEJOINSERTMATCHPLANALYZE */
-/* PRAGMABORTUPDATEVALUESVIRTUALWAYSWHENWHERECURSIVEAFTERENAMEAND */
-/* EFERREDISTINCTAUTOINCREMENTCASTCOLUMNCOMMITCONFLICTCROSS */
-/* CURRENT_TIMESTAMPARTITIONDROPRECEDINGFAILASTFILTEREPLACEFIRST */
-/* FOLLOWINGFROMFULLIMITIFORDERESTRICTOTHERSOVERIGHTROLLBACKROWS */
-/* UNBOUNDEDUNIONUSINGVACUUMVIEWINDOWBYINITIALLYPRIMARY */
-static const char zKWText[647] = {
+/* PRAGMATERIALIZEDEFERREDISTINCTUPDATEVALUESVIRTUALWAYSWHENWHERE */
+/* CURSIVEABORTAFTERENAMEANDROPARTITIONAUTOINCREMENTCASTCOLUMN */
+/* COMMITCONFLICTCROSSCURRENT_TIMESTAMPRECEDINGFAILASTFILTER */
+/* EPLACEFIRSTFOLLOWINGFROMFULLIMITIFORDERESTRICTOTHERSOVER */
+/* ETURNINGRIGHTROLLBACKROWSUNBOUNDEDUNIONUSINGVACUUMVIEWINDOWBY */
+/* INITIALLYPRIMARY */
+static const char zKWText[666] = {
'R','E','I','N','D','E','X','E','D','E','S','C','A','P','E','A','C','H',
'E','C','K','E','Y','B','E','F','O','R','E','I','G','N','O','R','E','G',
'E','X','P','L','A','I','N','S','T','E','A','D','D','A','T','A','B','A',
@@ -159454,86 +163533,87 @@ static const char zKWText[647] = {
'C','R','E','A','T','E','C','U','R','R','E','N','T','_','D','A','T','E',
'I','M','M','E','D','I','A','T','E','J','O','I','N','S','E','R','T','M',
'A','T','C','H','P','L','A','N','A','L','Y','Z','E','P','R','A','G','M',
- 'A','B','O','R','T','U','P','D','A','T','E','V','A','L','U','E','S','V',
- 'I','R','T','U','A','L','W','A','Y','S','W','H','E','N','W','H','E','R',
- 'E','C','U','R','S','I','V','E','A','F','T','E','R','E','N','A','M','E',
- 'A','N','D','E','F','E','R','R','E','D','I','S','T','I','N','C','T','A',
- 'U','T','O','I','N','C','R','E','M','E','N','T','C','A','S','T','C','O',
- 'L','U','M','N','C','O','M','M','I','T','C','O','N','F','L','I','C','T',
- 'C','R','O','S','S','C','U','R','R','E','N','T','_','T','I','M','E','S',
- 'T','A','M','P','A','R','T','I','T','I','O','N','D','R','O','P','R','E',
- 'C','E','D','I','N','G','F','A','I','L','A','S','T','F','I','L','T','E',
- 'R','E','P','L','A','C','E','F','I','R','S','T','F','O','L','L','O','W',
- 'I','N','G','F','R','O','M','F','U','L','L','I','M','I','T','I','F','O',
- 'R','D','E','R','E','S','T','R','I','C','T','O','T','H','E','R','S','O',
- 'V','E','R','I','G','H','T','R','O','L','L','B','A','C','K','R','O','W',
- 'S','U','N','B','O','U','N','D','E','D','U','N','I','O','N','U','S','I',
- 'N','G','V','A','C','U','U','M','V','I','E','W','I','N','D','O','W','B',
- 'Y','I','N','I','T','I','A','L','L','Y','P','R','I','M','A','R','Y',
+ 'A','T','E','R','I','A','L','I','Z','E','D','E','F','E','R','R','E','D',
+ 'I','S','T','I','N','C','T','U','P','D','A','T','E','V','A','L','U','E',
+ 'S','V','I','R','T','U','A','L','W','A','Y','S','W','H','E','N','W','H',
+ 'E','R','E','C','U','R','S','I','V','E','A','B','O','R','T','A','F','T',
+ 'E','R','E','N','A','M','E','A','N','D','R','O','P','A','R','T','I','T',
+ 'I','O','N','A','U','T','O','I','N','C','R','E','M','E','N','T','C','A',
+ 'S','T','C','O','L','U','M','N','C','O','M','M','I','T','C','O','N','F',
+ 'L','I','C','T','C','R','O','S','S','C','U','R','R','E','N','T','_','T',
+ 'I','M','E','S','T','A','M','P','R','E','C','E','D','I','N','G','F','A',
+ 'I','L','A','S','T','F','I','L','T','E','R','E','P','L','A','C','E','F',
+ 'I','R','S','T','F','O','L','L','O','W','I','N','G','F','R','O','M','F',
+ 'U','L','L','I','M','I','T','I','F','O','R','D','E','R','E','S','T','R',
+ 'I','C','T','O','T','H','E','R','S','O','V','E','R','E','T','U','R','N',
+ 'I','N','G','R','I','G','H','T','R','O','L','L','B','A','C','K','R','O',
+ 'W','S','U','N','B','O','U','N','D','E','D','U','N','I','O','N','U','S',
+ 'I','N','G','V','A','C','U','U','M','V','I','E','W','I','N','D','O','W',
+ 'B','Y','I','N','I','T','I','A','L','L','Y','P','R','I','M','A','R','Y',
};
/* aKWHash[i] is the hash value for the i-th keyword */
static const unsigned char aKWHash[127] = {
- 84, 102, 132, 82, 114, 29, 0, 0, 91, 0, 85, 72, 0,
- 53, 35, 86, 15, 0, 42, 94, 54, 126, 133, 19, 0, 0,
- 138, 0, 40, 128, 0, 22, 104, 0, 9, 0, 0, 122, 80,
- 0, 78, 6, 0, 65, 99, 145, 0, 134, 112, 0, 0, 48,
- 0, 100, 24, 0, 17, 0, 27, 70, 23, 26, 5, 60, 140,
- 107, 121, 0, 73, 101, 71, 143, 61, 119, 74, 0, 49, 0,
- 11, 41, 0, 110, 0, 0, 0, 106, 10, 108, 113, 124, 14,
- 50, 123, 0, 89, 0, 18, 120, 142, 56, 129, 137, 88, 83,
- 37, 30, 125, 0, 0, 105, 51, 130, 127, 0, 34, 0, 0,
- 44, 0, 95, 38, 39, 0, 20, 45, 116, 90,
+ 84, 92, 134, 82, 105, 29, 0, 0, 94, 0, 85, 72, 0,
+ 53, 35, 86, 15, 0, 42, 97, 54, 89, 135, 19, 0, 0,
+ 140, 0, 40, 129, 0, 22, 107, 0, 9, 0, 0, 123, 80,
+ 0, 78, 6, 0, 65, 103, 147, 0, 136, 115, 0, 0, 48,
+ 0, 90, 24, 0, 17, 0, 27, 70, 23, 26, 5, 60, 142,
+ 110, 122, 0, 73, 91, 71, 145, 61, 120, 74, 0, 49, 0,
+ 11, 41, 0, 113, 0, 0, 0, 109, 10, 111, 116, 125, 14,
+ 50, 124, 0, 100, 0, 18, 121, 144, 56, 130, 139, 88, 83,
+ 37, 30, 126, 0, 0, 108, 51, 131, 128, 0, 34, 0, 0,
+ 132, 0, 98, 38, 39, 0, 20, 45, 117, 93,
};
/* aKWNext[] forms the hash collision chain. If aKWHash[i]==0
** then the i-th keyword has no more hash collisions. Otherwise,
** the next keyword with the same hash is aKWHash[i]-1. */
-static const unsigned char aKWNext[145] = {
- 0, 0, 0, 0, 4, 0, 43, 0, 0, 103, 111, 0, 0,
- 0, 2, 0, 0, 141, 0, 0, 0, 13, 0, 0, 0, 0,
- 139, 0, 0, 118, 52, 0, 0, 135, 12, 0, 0, 62, 0,
- 136, 0, 131, 0, 0, 36, 0, 0, 28, 77, 0, 0, 0,
+static const unsigned char aKWNext[147] = {
+ 0, 0, 0, 0, 4, 0, 43, 0, 0, 106, 114, 0, 0,
+ 0, 2, 0, 0, 143, 0, 0, 0, 13, 0, 0, 0, 0,
+ 141, 0, 0, 119, 52, 0, 0, 137, 12, 0, 0, 62, 0,
+ 138, 0, 133, 0, 0, 36, 0, 0, 28, 77, 0, 0, 0,
0, 59, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 69, 0, 0, 0, 0, 0, 144, 3, 0, 58, 0, 1,
- 75, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 64, 66,
- 63, 0, 0, 0, 0, 46, 0, 16, 0, 115, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 81, 97, 0, 8, 0, 109,
- 21, 7, 67, 0, 79, 93, 117, 0, 0, 68, 0, 0, 96,
- 0, 55, 0, 76, 0, 92, 32, 33, 57, 25, 0, 98, 0,
- 0, 87,
+ 0, 69, 0, 0, 0, 0, 0, 146, 3, 0, 58, 0, 1,
+ 75, 0, 0, 0, 31, 0, 0, 0, 0, 0, 127, 0, 104,
+ 0, 64, 66, 63, 0, 0, 0, 0, 0, 46, 0, 16, 8,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 101, 0,
+ 112, 21, 7, 67, 0, 79, 96, 118, 0, 0, 68, 0, 0,
+ 99, 44, 0, 55, 0, 76, 0, 95, 32, 33, 57, 25, 0,
+ 102, 0, 0, 87,
};
/* aKWLen[i] is the length (in bytes) of the i-th keyword */
-static const unsigned char aKWLen[145] = {
+static const unsigned char aKWLen[147] = {
7, 7, 5, 4, 6, 4, 5, 3, 6, 7, 3, 6, 6,
7, 7, 3, 8, 2, 6, 5, 4, 4, 3, 10, 4, 7,
6, 9, 4, 2, 6, 5, 9, 9, 4, 7, 3, 2, 4,
4, 6, 11, 6, 2, 7, 5, 5, 9, 6, 10, 4, 6,
2, 3, 7, 5, 9, 6, 6, 4, 5, 5, 10, 6, 5,
7, 4, 5, 7, 6, 7, 7, 6, 5, 7, 3, 7, 4,
- 7, 6, 12, 9, 4, 6, 5, 4, 7, 6, 5, 6, 6,
- 7, 6, 4, 5, 9, 5, 6, 3, 8, 8, 2, 13, 2,
- 2, 4, 6, 6, 8, 5, 17, 12, 7, 9, 4, 9, 4,
- 4, 6, 7, 5, 9, 4, 4, 5, 2, 5, 8, 6, 4,
- 5, 8, 4, 3, 9, 5, 5, 6, 4, 6, 2, 2, 9,
- 3, 7,
+ 7, 6, 12, 9, 4, 6, 5, 4, 7, 6, 12, 8, 8,
+ 2, 6, 6, 7, 6, 4, 5, 9, 5, 5, 6, 3, 4,
+ 9, 13, 2, 2, 4, 6, 6, 8, 5, 17, 12, 7, 9,
+ 4, 4, 6, 7, 5, 9, 4, 4, 5, 2, 5, 8, 6,
+ 4, 9, 5, 8, 4, 3, 9, 5, 5, 6, 4, 6, 2,
+ 2, 9, 3, 7,
};
/* aKWOffset[i] is the index into zKWText[] of the start of
** the text for the i-th keyword. */
-static const unsigned short int aKWOffset[145] = {
+static const unsigned short int aKWOffset[147] = {
0, 2, 2, 8, 9, 14, 16, 20, 23, 25, 25, 29, 33,
36, 41, 46, 48, 53, 54, 59, 62, 65, 67, 69, 78, 81,
86, 90, 90, 94, 99, 101, 105, 111, 119, 123, 123, 123, 126,
129, 132, 137, 142, 146, 147, 152, 156, 160, 168, 174, 181, 184,
184, 187, 189, 195, 198, 206, 211, 216, 219, 222, 226, 236, 239,
244, 244, 248, 252, 259, 265, 271, 277, 277, 283, 284, 288, 295,
- 299, 306, 312, 324, 333, 335, 341, 346, 348, 355, 360, 365, 371,
- 377, 382, 388, 392, 395, 404, 408, 414, 416, 423, 424, 431, 433,
- 435, 444, 448, 454, 460, 468, 473, 473, 473, 489, 498, 501, 510,
- 513, 517, 522, 529, 534, 543, 547, 550, 555, 557, 561, 569, 575,
- 578, 583, 591, 591, 595, 604, 609, 614, 620, 623, 626, 629, 631,
- 636, 640,
+ 299, 306, 312, 324, 333, 335, 341, 346, 348, 355, 359, 370, 377,
+ 378, 385, 391, 397, 402, 408, 412, 415, 424, 429, 433, 439, 441,
+ 444, 453, 455, 457, 466, 470, 476, 482, 490, 495, 495, 495, 511,
+ 520, 523, 527, 532, 539, 544, 553, 557, 560, 565, 567, 571, 579,
+ 585, 588, 597, 602, 610, 610, 614, 623, 628, 633, 639, 642, 645,
+ 648, 650, 655, 659,
};
/* aKWCode[i] is the parser symbol code for the i-th keyword */
-static const unsigned char aKWCode[145] = {
+static const unsigned char aKWCode[147] = {
TK_REINDEX, TK_INDEXED, TK_INDEX, TK_DESC, TK_ESCAPE,
TK_EACH, TK_CHECK, TK_KEY, TK_BEFORE, TK_FOREIGN,
TK_FOR, TK_IGNORE, TK_LIKE_KW, TK_EXPLAIN, TK_INSTEAD,
@@ -159551,18 +163631,19 @@ static const unsigned char aKWCode[145] = {
TK_BETWEEN, TK_NOTHING, TK_GROUPS, TK_GROUP, TK_CASCADE,
TK_ASC, TK_DEFAULT, TK_CASE, TK_COLLATE, TK_CREATE,
TK_CTIME_KW, TK_IMMEDIATE, TK_JOIN, TK_INSERT, TK_MATCH,
- TK_PLAN, TK_ANALYZE, TK_PRAGMA, TK_ABORT, TK_UPDATE,
- TK_VALUES, TK_VIRTUAL, TK_ALWAYS, TK_WHEN, TK_WHERE,
- TK_RECURSIVE, TK_AFTER, TK_RENAME, TK_AND, TK_DEFERRED,
- TK_DISTINCT, TK_IS, TK_AUTOINCR, TK_TO, TK_IN,
- TK_CAST, TK_COLUMNKW, TK_COMMIT, TK_CONFLICT, TK_JOIN_KW,
- TK_CTIME_KW, TK_CTIME_KW, TK_CURRENT, TK_PARTITION, TK_DROP,
- TK_PRECEDING, TK_FAIL, TK_LAST, TK_FILTER, TK_REPLACE,
- TK_FIRST, TK_FOLLOWING, TK_FROM, TK_JOIN_KW, TK_LIMIT,
- TK_IF, TK_ORDER, TK_RESTRICT, TK_OTHERS, TK_OVER,
- TK_JOIN_KW, TK_ROLLBACK, TK_ROWS, TK_ROW, TK_UNBOUNDED,
- TK_UNION, TK_USING, TK_VACUUM, TK_VIEW, TK_WINDOW,
- TK_DO, TK_BY, TK_INITIALLY, TK_ALL, TK_PRIMARY,
+ TK_PLAN, TK_ANALYZE, TK_PRAGMA, TK_MATERIALIZED, TK_DEFERRED,
+ TK_DISTINCT, TK_IS, TK_UPDATE, TK_VALUES, TK_VIRTUAL,
+ TK_ALWAYS, TK_WHEN, TK_WHERE, TK_RECURSIVE, TK_ABORT,
+ TK_AFTER, TK_RENAME, TK_AND, TK_DROP, TK_PARTITION,
+ TK_AUTOINCR, TK_TO, TK_IN, TK_CAST, TK_COLUMNKW,
+ TK_COMMIT, TK_CONFLICT, TK_JOIN_KW, TK_CTIME_KW, TK_CTIME_KW,
+ TK_CURRENT, TK_PRECEDING, TK_FAIL, TK_LAST, TK_FILTER,
+ TK_REPLACE, TK_FIRST, TK_FOLLOWING, TK_FROM, TK_JOIN_KW,
+ TK_LIMIT, TK_IF, TK_ORDER, TK_RESTRICT, TK_OTHERS,
+ TK_OVER, TK_RETURNING, TK_JOIN_KW, TK_ROLLBACK, TK_ROWS,
+ TK_ROW, TK_UNBOUNDED, TK_UNION, TK_USING, TK_VACUUM,
+ TK_VIEW, TK_WINDOW, TK_DO, TK_BY, TK_INITIALLY,
+ TK_ALL, TK_PRIMARY,
};
/* Hash table decoded:
** 0: INSERT
@@ -159586,7 +163667,7 @@ static const unsigned char aKWCode[145] = {
** 18: TRANSACTION RIGHT
** 19: WHEN
** 20: SET HAVING
-** 21: IF
+** 21: MATERIALIZED IF
** 22: ROWS
** 23: SELECT
** 24:
@@ -159682,7 +163763,7 @@ static const unsigned char aKWCode[145] = {
** 114: INTERSECT UNBOUNDED
** 115:
** 116:
-** 117: ON
+** 117: RETURNING ON
** 118:
** 119: WHERE
** 120: NO INNER
@@ -159700,7 +163781,7 @@ static int keywordCode(const char *z, int n, int *pType){
int i, j;
const char *zKW;
if( n>=2 ){
- i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n) % 127;
+ i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n*1) % 127;
for(i=((int)aKWHash[i])-1; i>=0; i=((int)aKWNext[i])-1){
if( aKWLen[i]!=n ) continue;
zKW = &zKWText[aKWOffset[i]];
@@ -159805,63 +163886,65 @@ static int keywordCode(const char *z, int n, int *pType){
testcase( i==85 ); /* PLAN */
testcase( i==86 ); /* ANALYZE */
testcase( i==87 ); /* PRAGMA */
- testcase( i==88 ); /* ABORT */
- testcase( i==89 ); /* UPDATE */
- testcase( i==90 ); /* VALUES */
- testcase( i==91 ); /* VIRTUAL */
- testcase( i==92 ); /* ALWAYS */
- testcase( i==93 ); /* WHEN */
- testcase( i==94 ); /* WHERE */
- testcase( i==95 ); /* RECURSIVE */
- testcase( i==96 ); /* AFTER */
- testcase( i==97 ); /* RENAME */
- testcase( i==98 ); /* AND */
- testcase( i==99 ); /* DEFERRED */
- testcase( i==100 ); /* DISTINCT */
- testcase( i==101 ); /* IS */
- testcase( i==102 ); /* AUTOINCREMENT */
- testcase( i==103 ); /* TO */
- testcase( i==104 ); /* IN */
- testcase( i==105 ); /* CAST */
- testcase( i==106 ); /* COLUMN */
- testcase( i==107 ); /* COMMIT */
- testcase( i==108 ); /* CONFLICT */
- testcase( i==109 ); /* CROSS */
- testcase( i==110 ); /* CURRENT_TIMESTAMP */
- testcase( i==111 ); /* CURRENT_TIME */
- testcase( i==112 ); /* CURRENT */
- testcase( i==113 ); /* PARTITION */
- testcase( i==114 ); /* DROP */
- testcase( i==115 ); /* PRECEDING */
- testcase( i==116 ); /* FAIL */
- testcase( i==117 ); /* LAST */
- testcase( i==118 ); /* FILTER */
- testcase( i==119 ); /* REPLACE */
- testcase( i==120 ); /* FIRST */
- testcase( i==121 ); /* FOLLOWING */
- testcase( i==122 ); /* FROM */
- testcase( i==123 ); /* FULL */
- testcase( i==124 ); /* LIMIT */
- testcase( i==125 ); /* IF */
- testcase( i==126 ); /* ORDER */
- testcase( i==127 ); /* RESTRICT */
- testcase( i==128 ); /* OTHERS */
- testcase( i==129 ); /* OVER */
- testcase( i==130 ); /* RIGHT */
- testcase( i==131 ); /* ROLLBACK */
- testcase( i==132 ); /* ROWS */
- testcase( i==133 ); /* ROW */
- testcase( i==134 ); /* UNBOUNDED */
- testcase( i==135 ); /* UNION */
- testcase( i==136 ); /* USING */
- testcase( i==137 ); /* VACUUM */
- testcase( i==138 ); /* VIEW */
- testcase( i==139 ); /* WINDOW */
- testcase( i==140 ); /* DO */
- testcase( i==141 ); /* BY */
- testcase( i==142 ); /* INITIALLY */
- testcase( i==143 ); /* ALL */
- testcase( i==144 ); /* PRIMARY */
+ testcase( i==88 ); /* MATERIALIZED */
+ testcase( i==89 ); /* DEFERRED */
+ testcase( i==90 ); /* DISTINCT */
+ testcase( i==91 ); /* IS */
+ testcase( i==92 ); /* UPDATE */
+ testcase( i==93 ); /* VALUES */
+ testcase( i==94 ); /* VIRTUAL */
+ testcase( i==95 ); /* ALWAYS */
+ testcase( i==96 ); /* WHEN */
+ testcase( i==97 ); /* WHERE */
+ testcase( i==98 ); /* RECURSIVE */
+ testcase( i==99 ); /* ABORT */
+ testcase( i==100 ); /* AFTER */
+ testcase( i==101 ); /* RENAME */
+ testcase( i==102 ); /* AND */
+ testcase( i==103 ); /* DROP */
+ testcase( i==104 ); /* PARTITION */
+ testcase( i==105 ); /* AUTOINCREMENT */
+ testcase( i==106 ); /* TO */
+ testcase( i==107 ); /* IN */
+ testcase( i==108 ); /* CAST */
+ testcase( i==109 ); /* COLUMN */
+ testcase( i==110 ); /* COMMIT */
+ testcase( i==111 ); /* CONFLICT */
+ testcase( i==112 ); /* CROSS */
+ testcase( i==113 ); /* CURRENT_TIMESTAMP */
+ testcase( i==114 ); /* CURRENT_TIME */
+ testcase( i==115 ); /* CURRENT */
+ testcase( i==116 ); /* PRECEDING */
+ testcase( i==117 ); /* FAIL */
+ testcase( i==118 ); /* LAST */
+ testcase( i==119 ); /* FILTER */
+ testcase( i==120 ); /* REPLACE */
+ testcase( i==121 ); /* FIRST */
+ testcase( i==122 ); /* FOLLOWING */
+ testcase( i==123 ); /* FROM */
+ testcase( i==124 ); /* FULL */
+ testcase( i==125 ); /* LIMIT */
+ testcase( i==126 ); /* IF */
+ testcase( i==127 ); /* ORDER */
+ testcase( i==128 ); /* RESTRICT */
+ testcase( i==129 ); /* OTHERS */
+ testcase( i==130 ); /* OVER */
+ testcase( i==131 ); /* RETURNING */
+ testcase( i==132 ); /* RIGHT */
+ testcase( i==133 ); /* ROLLBACK */
+ testcase( i==134 ); /* ROWS */
+ testcase( i==135 ); /* ROW */
+ testcase( i==136 ); /* UNBOUNDED */
+ testcase( i==137 ); /* UNION */
+ testcase( i==138 ); /* USING */
+ testcase( i==139 ); /* VACUUM */
+ testcase( i==140 ); /* VIEW */
+ testcase( i==141 ); /* WINDOW */
+ testcase( i==142 ); /* DO */
+ testcase( i==143 ); /* BY */
+ testcase( i==144 ); /* INITIALLY */
+ testcase( i==145 ); /* ALL */
+ testcase( i==146 ); /* PRIMARY */
*pType = aKWCode[i];
break;
}
@@ -159873,7 +163956,7 @@ SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char *z, int n){
keywordCode((char*)z, n, &id);
return id;
}
-#define SQLITE_N_KEYWORD 145
+#define SQLITE_N_KEYWORD 147
SQLITE_API int sqlite3_keyword_name(int i,const char **pzName,int *pnName){
if( i<0 || i>=SQLITE_N_KEYWORD ) return SQLITE_ERROR;
*pzName = zKWText + aKWOffset[i];
@@ -160242,7 +164325,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
if( n==0 ) *tokenType = TK_ILLEGAL;
return i;
}
- case CC_KYWD: {
+ case CC_KYWD0: {
for(i=1; aiClass[z[i]]<=CC_KYWD; i++){}
if( IdChar(z[i]) ){
/* This token started out using characters that can appear in keywords,
@@ -160272,10 +164355,19 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
** SQL keywords start with the letter 'x'. Fall through */
/* no break */ deliberate_fall_through
}
+ case CC_KYWD:
case CC_ID: {
i = 1;
break;
}
+ case CC_BOM: {
+ if( z[1]==0xbb && z[2]==0xbf ){
+ *tokenType = TK_SPACE;
+ return 3;
+ }
+ i = 1;
+ break;
+ }
case CC_NUL: {
*tokenType = TK_ILLEGAL;
return 0;
@@ -160454,19 +164546,7 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr
if( !IN_RENAME_OBJECT ){
sqlite3DeleteTrigger(db, pParse->pNewTrigger);
}
-
- if( pParse->pWithToFree ) sqlite3WithDelete(db, pParse->pWithToFree);
sqlite3DbFree(db, pParse->pVList);
- while( pParse->pAinc ){
- AutoincInfo *p = pParse->pAinc;
- pParse->pAinc = p->pNext;
- sqlite3DbFreeNN(db, p);
- }
- while( pParse->pZombieTab ){
- Table *p = pParse->pZombieTab;
- pParse->pZombieTab = p->pNextZombie;
- sqlite3DeleteTable(db, p);
- }
db->pParse = pParse->pParentParse;
pParse->pParentParse = 0;
assert( nErr==0 || pParse->rc!=SQLITE_OK );
@@ -161304,7 +165384,7 @@ SQLITE_API int sqlite3_initialize(void){
sqlite3GlobalConfig.isPCacheInit = 1;
rc = sqlite3OsInit();
}
-#ifdef SQLITE_ENABLE_DESERIALIZE
+#ifndef SQLITE_OMIT_DESERIALIZE
if( rc==SQLITE_OK ){
rc = sqlite3MemdbInit();
}
@@ -161719,12 +165799,12 @@ SQLITE_API int sqlite3_config(int op, ...){
}
#endif /* SQLITE_ENABLE_SORTER_REFERENCES */
-#ifdef SQLITE_ENABLE_DESERIALIZE
+#ifndef SQLITE_OMIT_DESERIALIZE
case SQLITE_CONFIG_MEMDB_MAXSIZE: {
sqlite3GlobalConfig.mxMemdbSize = va_arg(ap, sqlite3_int64);
break;
}
-#endif /* SQLITE_ENABLE_DESERIALIZE */
+#endif /* SQLITE_OMIT_DESERIALIZE */
default: {
rc = SQLITE_ERROR;
@@ -161896,7 +165976,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3 *db){
sqlite3BtreeEnterAll(db);
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
- if( pBt && sqlite3BtreeIsInTrans(pBt) ){
+ if( pBt && sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){
Pager *pPager = sqlite3BtreePager(pBt);
rc = sqlite3PagerFlush(pPager);
if( rc==SQLITE_BUSY ){
@@ -162241,9 +166321,39 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){
}
/*
+** Return the transaction state for a single databse, or the maximum
+** transaction state over all attached databases if zSchema is null.
+*/
+SQLITE_API int sqlite3_txn_state(sqlite3 *db, const char *zSchema){
+ int iDb, nDb;
+ int iTxn = -1;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return -1;
+ }
+#endif
+ sqlite3_mutex_enter(db->mutex);
+ if( zSchema ){
+ nDb = iDb = sqlite3FindDbName(db, zSchema);
+ if( iDb<0 ) nDb--;
+ }else{
+ iDb = 0;
+ nDb = db->nDb-1;
+ }
+ for(; iDb<=nDb; iDb++){
+ Btree *pBt = db->aDb[iDb].pBt;
+ int x = pBt!=0 ? sqlite3BtreeTxnState(pBt) : SQLITE_TXN_NONE;
+ if( x>iTxn ) iTxn = x;
+ }
+ sqlite3_mutex_leave(db->mutex);
+ return iTxn;
+}
+
+/*
** Two variations on the public interface for closing a database
** connection. The sqlite3_close() version returns SQLITE_BUSY and
-** leaves the connection option if there are unfinalized prepared
+** leaves the connection open if there are unfinalized prepared
** statements or unfinished sqlite3_backups. The sqlite3_close_v2()
** version forces the connection to become a zombie if there are
** unclosed resources, and arranges for deallocation when the last
@@ -162400,7 +166510,7 @@ SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db, int tripCode){
for(i=0; i<db->nDb; i++){
Btree *p = db->aDb[i].pBt;
if( p ){
- if( sqlite3BtreeIsInTrans(p) ){
+ if( sqlite3BtreeTxnState(p)==SQLITE_TXN_WRITE ){
inTrans = 1;
}
sqlite3BtreeRollback(p, tripCode, !schemaChange);
@@ -162853,6 +166963,10 @@ SQLITE_PRIVATE int sqlite3CreateFunc(
}else{
sqlite3ExpirePreparedStatements(db, 0);
}
+ }else if( xSFunc==0 && xFinal==0 ){
+ /* Trying to delete a function that does not exist. This is a no-op.
+ ** https://sqlite.org/forum/forumpost/726219164b */
+ return SQLITE_OK;
}
p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 1);
@@ -163343,7 +167457,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
return SQLITE_OK;
#else
int rc; /* Return code */
- int iDb = SQLITE_MAX_ATTACHED; /* sqlite3.aDb[] index of db to checkpoint */
+ int iDb; /* Schema to checkpoint */
#ifdef SQLITE_ENABLE_API_ARMOR
if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
@@ -163366,6 +167480,8 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
sqlite3_mutex_enter(db->mutex);
if( zDb && zDb[0] ){
iDb = sqlite3FindDbName(db, zDb);
+ }else{
+ iDb = SQLITE_MAX_DB; /* This means process all schemas */
}
if( iDb<0 ){
rc = SQLITE_ERROR;
@@ -163414,7 +167530,7 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){
** associated with the specific b-tree being checkpointed is taken by
** this function while the checkpoint is running.
**
-** If iDb is passed SQLITE_MAX_ATTACHED, then all attached databases are
+** If iDb is passed SQLITE_MAX_DB then all attached databases are
** checkpointed. If an error is encountered it is returned immediately -
** no attempt is made to checkpoint any remaining databases.
**
@@ -163429,9 +167545,11 @@ SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3 *db, int iDb, int eMode, int *pnLog
assert( sqlite3_mutex_held(db->mutex) );
assert( !pnLog || *pnLog==-1 );
assert( !pnCkpt || *pnCkpt==-1 );
+ testcase( iDb==SQLITE_MAX_ATTACHED ); /* See forum post a006d86f72 */
+ testcase( iDb==SQLITE_MAX_DB );
for(i=0; i<db->nDb && rc==SQLITE_OK; i++){
- if( i==iDb || iDb==SQLITE_MAX_ATTACHED ){
+ if( i==iDb || iDb==SQLITE_MAX_DB ){
rc = sqlite3BtreeCheckpoint(db->aDb[i].pBt, eMode, pnLog, pnCkpt);
pnLog = 0;
pnCkpt = 0;
@@ -164821,7 +168939,9 @@ SQLITE_API int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, vo
}
rc = SQLITE_OK;
}else{
+ int nSave = db->busyHandler.nBusy;
rc = sqlite3OsFileControl(fd, op, pArg);
+ db->busyHandler.nBusy = nSave;
}
sqlite3BtreeLeave(pBtree);
}
@@ -165047,7 +169167,7 @@ SQLITE_API int sqlite3_test_control(int op, ...){
*/
case SQLITE_TESTCTRL_OPTIMIZATIONS: {
sqlite3 *db = va_arg(ap, sqlite3*);
- db->dbOptFlags = (u16)(va_arg(ap, int) & 0xffff);
+ db->dbOptFlags = va_arg(ap, u32);
break;
}
@@ -165204,6 +169324,74 @@ SQLITE_API int sqlite3_test_control(int op, ...){
sqlite3ResultIntReal(pCtx);
break;
}
+
+ /* sqlite3_test_control(SQLITE_TESTCTRL_SEEK_COUNT,
+ ** sqlite3 *db, // Database connection
+ ** u64 *pnSeek // Write seek count here
+ ** );
+ **
+ ** This test-control queries the seek-counter on the "main" database
+ ** file. The seek-counter is written into *pnSeek and is then reset.
+ ** The seek-count is only available if compiled with SQLITE_DEBUG.
+ */
+ case SQLITE_TESTCTRL_SEEK_COUNT: {
+ sqlite3 *db = va_arg(ap, sqlite3*);
+ u64 *pn = va_arg(ap, sqlite3_uint64*);
+ *pn = sqlite3BtreeSeekCount(db->aDb->pBt);
+ (void)db; /* Silence harmless unused variable warning */
+ break;
+ }
+
+ /* sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, op, ptr)
+ **
+ ** "ptr" is a pointer to a u32.
+ **
+ ** op==0 Store the current sqlite3SelectTrace in *ptr
+ ** op==1 Set sqlite3SelectTrace to the value *ptr
+ ** op==3 Store the current sqlite3WhereTrace in *ptr
+ ** op==3 Set sqlite3WhereTrace to the value *ptr
+ */
+ case SQLITE_TESTCTRL_TRACEFLAGS: {
+ int opTrace = va_arg(ap, int);
+ u32 *ptr = va_arg(ap, u32*);
+ switch( opTrace ){
+ case 0: *ptr = sqlite3SelectTrace; break;
+ case 1: sqlite3SelectTrace = *ptr; break;
+ case 2: *ptr = sqlite3WhereTrace; break;
+ case 3: sqlite3WhereTrace = *ptr; break;
+ }
+ break;
+ }
+
+#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_WSD)
+ /* sqlite3_test_control(SQLITE_TESTCTRL_TUNE, id, *piValue)
+ **
+ ** If "id" is an integer between 1 and SQLITE_NTUNE then set the value
+ ** of the id-th tuning parameter to *piValue. If "id" is between -1
+ ** and -SQLITE_NTUNE, then write the current value of the (-id)-th
+ ** tuning parameter into *piValue.
+ **
+ ** Tuning parameters are for use during transient development builds,
+ ** to help find the best values for constants in the query planner.
+ ** Access tuning parameters using the Tuning(ID) macro. Set the
+ ** parameters in the CLI using ".testctrl tune ID VALUE".
+ **
+ ** Transient use only. Tuning parameters should not be used in
+ ** checked-in code.
+ */
+ case SQLITE_TESTCTRL_TUNE: {
+ int id = va_arg(ap, int);
+ int *piValue = va_arg(ap, int*);
+ if( id>0 && id<=SQLITE_NTUNE ){
+ Tuning(id) = *piValue;
+ }else if( id<0 && id>=-SQLITE_NTUNE ){
+ *piValue = Tuning(-id);
+ }else{
+ rc = SQLITE_NOTFOUND;
+ }
+ break;
+ }
+#endif
}
va_end(ap);
#endif /* SQLITE_UNTESTABLE */
@@ -165439,7 +169627,7 @@ SQLITE_API int sqlite3_snapshot_get(
int iDb = sqlite3FindDbName(db, zDb);
if( iDb==0 || iDb>1 ){
Btree *pBt = db->aDb[iDb].pBt;
- if( 0==sqlite3BtreeIsInTrans(pBt) ){
+ if( SQLITE_TXN_WRITE!=sqlite3BtreeTxnState(pBt) ){
rc = sqlite3BtreeBeginTrans(pBt, 0, 0);
if( rc==SQLITE_OK ){
rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot);
@@ -165475,10 +169663,10 @@ SQLITE_API int sqlite3_snapshot_open(
iDb = sqlite3FindDbName(db, zDb);
if( iDb==0 || iDb>1 ){
Btree *pBt = db->aDb[iDb].pBt;
- if( sqlite3BtreeIsInTrans(pBt)==0 ){
+ if( sqlite3BtreeTxnState(pBt)!=SQLITE_TXN_WRITE ){
Pager *pPager = sqlite3BtreePager(pBt);
int bUnlock = 0;
- if( sqlite3BtreeIsInReadTrans(pBt) ){
+ if( sqlite3BtreeTxnState(pBt)!=SQLITE_TXN_NONE ){
if( db->nVdbeActive==0 ){
rc = sqlite3PagerSnapshotCheck(pPager, pSnapshot);
if( rc==SQLITE_OK ){
@@ -165527,7 +169715,7 @@ SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){
iDb = sqlite3FindDbName(db, zDb);
if( iDb==0 || iDb>1 ){
Btree *pBt = db->aDb[iDb].pBt;
- if( 0==sqlite3BtreeIsInReadTrans(pBt) ){
+ if( SQLITE_TXN_NONE==sqlite3BtreeTxnState(pBt) ){
rc = sqlite3BtreeBeginTrans(pBt, 0, 0);
if( rc==SQLITE_OK ){
rc = sqlite3PagerSnapshotRecover(sqlite3BtreePager(pBt));
@@ -166646,7 +170834,7 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const voi
** is used for assert() conditions that are true only if it can be
** guranteed that the database is not corrupt.
*/
-#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
+#ifdef SQLITE_DEBUG
SQLITE_API extern int sqlite3_fts3_may_be_corrupt;
# define assert_fts3_nc(x) assert(sqlite3_fts3_may_be_corrupt || (x))
#else
@@ -167202,7 +171390,9 @@ SQLITE_PRIVATE int sqlite3Fts3Never(int b) { assert( !b ); return b; }
** assert() conditions in the fts3 code are activated - conditions that are
** only true if it is guaranteed that the fts3 database is not corrupt.
*/
+#ifdef SQLITE_DEBUG
SQLITE_API int sqlite3_fts3_may_be_corrupt = 1;
+#endif
/*
** Write a 64-bit variable-length integer to memory starting at p[0].
@@ -168773,7 +172963,7 @@ static int fts3ScanInteriorNode(
char *zBuffer = 0; /* Buffer to load terms into */
i64 nAlloc = 0; /* Size of allocated buffer */
int isFirstTerm = 1; /* True when processing first term on page */
- sqlite3_int64 iChild; /* Block id of child node to descend to */
+ u64 iChild; /* Block id of child node to descend to */
int nBuffer = 0; /* Total term size */
/* Skip over the 'height' varint that occurs at the start of every
@@ -168789,8 +172979,8 @@ static int fts3ScanInteriorNode(
** table, then there are always 20 bytes of zeroed padding following the
** nNode bytes of content (see sqlite3Fts3ReadBlock() for details).
*/
- zCsr += sqlite3Fts3GetVarint(zCsr, &iChild);
- zCsr += sqlite3Fts3GetVarint(zCsr, &iChild);
+ zCsr += sqlite3Fts3GetVarintU(zCsr, &iChild);
+ zCsr += sqlite3Fts3GetVarintU(zCsr, &iChild);
if( zCsr>zEnd ){
return FTS_CORRUPT_VTAB;
}
@@ -168843,20 +173033,20 @@ static int fts3ScanInteriorNode(
*/
cmp = memcmp(zTerm, zBuffer, (nBuffer>nTerm ? nTerm : nBuffer));
if( piFirst && (cmp<0 || (cmp==0 && nBuffer>nTerm)) ){
- *piFirst = iChild;
+ *piFirst = (i64)iChild;
piFirst = 0;
}
if( piLast && cmp<0 ){
- *piLast = iChild;
+ *piLast = (i64)iChild;
piLast = 0;
}
iChild++;
};
- if( piFirst ) *piFirst = iChild;
- if( piLast ) *piLast = iChild;
+ if( piFirst ) *piFirst = (i64)iChild;
+ if( piLast ) *piLast = (i64)iChild;
finish_scan:
sqlite3_free(zBuffer);
@@ -170462,14 +174652,20 @@ static int fts3SetHasStat(Fts3Table *p){
*/
static int fts3BeginMethod(sqlite3_vtab *pVtab){
Fts3Table *p = (Fts3Table*)pVtab;
+ int rc;
UNUSED_PARAMETER(pVtab);
assert( p->pSegments==0 );
assert( p->nPendingData==0 );
assert( p->inTransaction!=1 );
- TESTONLY( p->inTransaction = 1 );
- TESTONLY( p->mxSavepoint = -1; );
p->nLeafAdd = 0;
- return fts3SetHasStat(p);
+ rc = fts3SetHasStat(p);
+#ifdef SQLITE_DEBUG
+ if( rc==SQLITE_OK ){
+ p->inTransaction = 1;
+ p->mxSavepoint = -1;
+ }
+#endif
+ return rc;
}
/*
@@ -171998,16 +176194,15 @@ static int fts3EvalStart(Fts3Cursor *pCsr){
#ifndef SQLITE_DISABLE_FTS4_DEFERRED
if( rc==SQLITE_OK && nToken>1 && pTab->bFts4 ){
Fts3TokenAndCost *aTC;
- Fts3Expr **apOr;
aTC = (Fts3TokenAndCost *)sqlite3_malloc64(
sizeof(Fts3TokenAndCost) * nToken
+ sizeof(Fts3Expr *) * nOr * 2
);
- apOr = (Fts3Expr **)&aTC[nToken];
if( !aTC ){
rc = SQLITE_NOMEM;
}else{
+ Fts3Expr **apOr = (Fts3Expr **)&aTC[nToken];
int ii;
Fts3TokenAndCost *pTC = aTC;
Fts3Expr **ppOr = apOr;
@@ -172088,9 +176283,9 @@ static int fts3EvalNearTrim(
);
if( res ){
nNew = (int)(pOut - pPhrase->doclist.pList) - 1;
- if( nNew>=0 ){
+ assert_fts3_nc( nNew<=pPhrase->doclist.nList && nNew>0 );
+ if( nNew>=0 && nNew<=pPhrase->doclist.nList ){
assert( pPhrase->doclist.pList[nNew]=='\0' );
- assert( nNew<=pPhrase->doclist.nList && nNew>0 );
memset(&pPhrase->doclist.pList[nNew], 0, pPhrase->doclist.nList - nNew);
pPhrase->doclist.nList = nNew;
}
@@ -173383,6 +177578,7 @@ static int fts3auxFilterMethod(
sqlite3Fts3SegReaderFinish(&pCsr->csr);
sqlite3_free((void *)pCsr->filter.zTerm);
sqlite3_free(pCsr->aStat);
+ sqlite3_free(pCsr->zStop);
memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr);
pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
@@ -174024,6 +178220,11 @@ static int getNextNode(
if( *zInput=='(' ){
int nConsumed = 0;
pParse->nNest++;
+#if !defined(SQLITE_MAX_EXPR_DEPTH)
+ if( pParse->nNest>1000 ) return SQLITE_ERROR;
+#elif SQLITE_MAX_EXPR_DEPTH>0
+ if( pParse->nNest>SQLITE_MAX_EXPR_DEPTH ) return SQLITE_ERROR;
+#endif
rc = fts3ExprParse(pParse, zInput+1, nInput-1, ppExpr, &nConsumed);
*pnConsumed = (int)(zInput - z) + 1 + nConsumed;
return rc;
@@ -178899,7 +183100,7 @@ static int fts3SegReaderCmp(Fts3SegReader *pLhs, Fts3SegReader *pRhs){
if( rc==0 ){
rc = pRhs->iIdx - pLhs->iIdx;
}
- assert( rc!=0 );
+ assert_fts3_nc( rc!=0 );
return rc;
}
@@ -179095,8 +183296,8 @@ static int fts3PrefixCompress(
int nNext /* Size of buffer zNext in bytes */
){
int n;
- UNUSED_PARAMETER(nNext);
- for(n=0; n<nPrev && zPrev[n]==zNext[n]; n++);
+ for(n=0; n<nPrev && n<nNext && zPrev[n]==zNext[n]; n++);
+ assert_fts3_nc( n<nNext );
return n;
}
@@ -180095,7 +184296,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
nByte = sqlite3Fts3VarintLen(iDelta) + (isRequirePos?nList+1:0);
- rc = fts3GrowSegReaderBuffer(pCsr, nByte+nDoclist);
+ rc = fts3GrowSegReaderBuffer(pCsr, nByte+nDoclist+FTS3_NODE_PADDING);
if( rc ) return rc;
if( isFirst ){
@@ -181427,17 +185628,20 @@ static int fts3IncrmergeLoad(
while( reader.aNode && rc==SQLITE_OK ) rc = nodeReaderNext(&reader);
blobGrowBuffer(&pNode->key, reader.term.n, &rc);
if( rc==SQLITE_OK ){
- memcpy(pNode->key.a, reader.term.a, reader.term.n);
+ assert_fts3_nc( reader.term.n>0 || reader.aNode==0 );
+ if( reader.term.n>0 ){
+ memcpy(pNode->key.a, reader.term.a, reader.term.n);
+ }
pNode->key.n = reader.term.n;
if( i>0 ){
char *aBlock = 0;
int nBlock = 0;
pNode = &pWriter->aNodeWriter[i-1];
pNode->iBlock = reader.iChild;
- rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock, 0);
+ rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock,0);
blobGrowBuffer(&pNode->block,
MAX(nBlock, p->nNodeSize)+FTS3_NODE_PADDING, &rc
- );
+ );
if( rc==SQLITE_OK ){
memcpy(pNode->block.a, aBlock, nBlock);
pNode->block.n = nBlock;
@@ -182906,6 +187110,10 @@ SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *p){
/* #include <string.h> */
/* #include <assert.h> */
+#ifndef SQLITE_AMALGAMATION
+typedef sqlite3_int64 i64;
+#endif
+
/*
** Characters that may appear in the second argument to matchinfo().
*/
@@ -182956,9 +187164,9 @@ struct SnippetIter {
struct SnippetPhrase {
int nToken; /* Number of tokens in phrase */
char *pList; /* Pointer to start of phrase position list */
- int iHead; /* Next value in position list */
+ i64 iHead; /* Next value in position list */
char *pHead; /* Position list data following iHead */
- int iTail; /* Next value in trailing position list */
+ i64 iTail; /* Next value in trailing position list */
char *pTail; /* Position list data following iTail */
};
@@ -183123,7 +187331,7 @@ SQLITE_PRIVATE void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p){
** After it returns, *piPos contains the value of the next element of the
** list and *pp is advanced to the following varint.
*/
-static void fts3GetDeltaPosition(char **pp, int *piPos){
+static void fts3GetDeltaPosition(char **pp, i64 *piPos){
int iVal;
*pp += fts3GetVarint32(*pp, &iVal);
*piPos += (iVal-2);
@@ -183232,10 +187440,10 @@ static int fts3ExprPhraseCount(Fts3Expr *pExpr){
** arguments so that it points to the first element with a value greater
** than or equal to parameter iNext.
*/
-static void fts3SnippetAdvance(char **ppIter, int *piIter, int iNext){
+static void fts3SnippetAdvance(char **ppIter, i64 *piIter, int iNext){
char *pIter = *ppIter;
if( pIter ){
- int iIter = *piIter;
+ i64 iIter = *piIter;
while( iIter<iNext ){
if( 0==(*pIter & 0xFE) ){
@@ -183318,7 +187526,7 @@ static void fts3SnippetDetails(
SnippetPhrase *pPhrase = &pIter->aPhrase[i];
if( pPhrase->pTail ){
char *pCsr = pPhrase->pTail;
- int iCsr = pPhrase->iTail;
+ i64 iCsr = pPhrase->iTail;
while( iCsr<(iStart+pIter->nSnippet) && iCsr>=iStart ){
int j;
@@ -183364,7 +187572,7 @@ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){
rc = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pCsr);
assert( rc==SQLITE_OK || pCsr==0 );
if( pCsr ){
- int iFirst = 0;
+ i64 iFirst = 0;
pPhrase->pList = pCsr;
fts3GetDeltaPosition(&pCsr, &iFirst);
if( iFirst<0 ){
@@ -184428,8 +188636,8 @@ typedef struct TermOffsetCtx TermOffsetCtx;
struct TermOffset {
char *pList; /* Position-list */
- int iPos; /* Position just read from pList */
- int iOff; /* Offset of this term from read positions */
+ i64 iPos; /* Position just read from pList */
+ i64 iOff; /* Offset of this term from read positions */
};
struct TermOffsetCtx {
@@ -184448,7 +188656,7 @@ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){
int nTerm; /* Number of tokens in phrase */
int iTerm; /* For looping through nTerm phrase terms */
char *pList; /* Pointer to position list for phrase */
- int iPos = 0; /* First position in position-list */
+ i64 iPos = 0; /* First position in position-list */
int rc;
UNUSED_PARAMETER(iPhrase);
@@ -184925,6 +189133,7 @@ static int unicodeOpen(
pCsr->aInput = (const unsigned char *)aInput;
if( aInput==0 ){
pCsr->nInput = 0;
+ pCsr->aInput = (const unsigned char*)"";
}else if( nInput<0 ){
pCsr->nInput = (int)strlen(aInput);
}else{
@@ -185724,7 +189933,7 @@ static void jsonAppendSeparator(JsonString *p){
*/
static void jsonAppendString(JsonString *p, const char *zIn, u32 N){
u32 i;
- if( (N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0 ) return;
+ if( zIn==0 || ((N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0) ) return;
p->zBuf[p->nUsed++] = '"';
for(i=0; i<N; i++){
unsigned char c = ((unsigned const char*)zIn)[i];
@@ -187323,8 +191532,8 @@ static void jsonArrayStep(
jsonAppendChar(pStr, '[');
}else if( pStr->nUsed>1 ){
jsonAppendChar(pStr, ',');
- pStr->pCtx = ctx;
}
+ pStr->pCtx = ctx;
jsonAppendValue(pStr, argv[0]);
}
}
@@ -187384,11 +191593,7 @@ static void jsonGroupInverse(
if( NEVER(!pStr) ) return;
#endif
z = pStr->zBuf;
- for(i=1; (c = z[i])!=',' || inStr || nNest; i++){
- if( i>=pStr->nUsed ){
- pStr->nUsed = 1;
- return;
- }
+ for(i=1; i<pStr->nUsed && ((c = z[i])!=',' || inStr || nNest); i++){
if( c=='"' ){
inStr = !inStr;
}else if( c=='\\' ){
@@ -187398,8 +191603,13 @@ static void jsonGroupInverse(
if( c=='}' || c==']' ) nNest--;
}
}
- pStr->nUsed -= i;
- memmove(&z[1], &z[i+1], (size_t)pStr->nUsed-1);
+ if( i<pStr->nUsed ){
+ pStr->nUsed -= i;
+ memmove(&z[1], &z[i+1], (size_t)pStr->nUsed-1);
+ z[pStr->nUsed] = 0;
+ }else{
+ pStr->nUsed = 1;
+ }
}
#else
# define jsonGroupInverse 0
@@ -187427,8 +191637,8 @@ static void jsonObjectStep(
jsonAppendChar(pStr, '{');
}else if( pStr->nUsed>1 ){
jsonAppendChar(pStr, ',');
- pStr->pCtx = ctx;
}
+ pStr->pCtx = ctx;
z = (const char*)sqlite3_value_text(argv[0]);
n = (u32)sqlite3_value_bytes(argv[0]);
jsonAppendString(pStr, z, n);
@@ -188818,7 +193028,7 @@ static int nodeAcquire(
** are the leaves, and so on. If the depth as specified on the root node
** is greater than RTREE_MAX_DEPTH, the r-tree structure must be corrupt.
*/
- if( pNode && iNode==1 ){
+ if( pNode && rc==SQLITE_OK && iNode==1 ){
pRtree->iDepth = readInt16(pNode->zData);
if( pRtree->iDepth>RTREE_MAX_DEPTH ){
rc = SQLITE_CORRUPT_VTAB;
@@ -191949,11 +196159,16 @@ static void rtreedepth(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){
UNUSED_PARAMETER(nArg);
if( sqlite3_value_type(apArg[0])!=SQLITE_BLOB
|| sqlite3_value_bytes(apArg[0])<2
+
){
sqlite3_result_error(ctx, "Invalid argument to rtreedepth()", -1);
}else{
u8 *zBlob = (u8 *)sqlite3_value_blob(apArg[0]);
- sqlite3_result_int(ctx, readInt16(zBlob));
+ if( zBlob ){
+ sqlite3_result_int(ctx, readInt16(zBlob));
+ }else{
+ sqlite3_result_error_nomem(ctx);
+ }
}
}
@@ -192739,6 +196954,10 @@ static GeoPoly *geopolyFuncParam(
){
const unsigned char *a = sqlite3_value_blob(pVal);
int nVertex;
+ if( a==0 ){
+ sqlite3_result_error_nomem(pCtx);
+ return 0;
+ }
nVertex = (a[1]<<16) + (a[2]<<8) + a[3];
if( (a[0]==0 || a[0]==1)
&& (nVertex*2*sizeof(GeoCoord) + 4)==(unsigned int)nByte
@@ -193112,7 +197331,7 @@ static GeoPoly *geopolyBBox(
aCoord[2].f = mnY;
aCoord[3].f = mxY;
}
- }else{
+ }else if( aCoord ){
memset(aCoord, 0, sizeof(RtreeCoord)*4);
}
return pOut;
@@ -193504,7 +197723,7 @@ static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2){
geopolyAddSegments(p, p1, 1);
geopolyAddSegments(p, p2, 2);
pThisEvent = geopolySortEventsByX(p->aEvent, p->nEvent);
- rX = pThisEvent->x==0.0 ? -1.0 : 0.0;
+ rX = pThisEvent && pThisEvent->x==0.0 ? -1.0 : 0.0;
memset(aOverlap, 0, sizeof(aOverlap));
while( pThisEvent ){
if( pThisEvent->x!=rX ){
@@ -197472,7 +201691,9 @@ char *rbuVacuumIndexStart(
zSep = "";
for(iCol=0; iCol<pIter->nCol; iCol++){
const char *zQuoted = (const char*)sqlite3_column_text(pSel, iCol);
- if( zQuoted[0]=='N' ){
+ if( zQuoted==0 ){
+ p->rc = SQLITE_NOMEM;
+ }else if( zQuoted[0]=='N' ){
bFailed = 1;
break;
}
@@ -200680,22 +204901,24 @@ static int rbuVfsShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
#endif
assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) );
- if( pRbu && (pRbu->eStage==RBU_STAGE_OAL || pRbu->eStage==RBU_STAGE_MOVE) ){
- /* Magic number 1 is the WAL_CKPT_LOCK lock. Preventing SQLite from
- ** taking this lock also prevents any checkpoints from occurring.
- ** todo: really, it's not clear why this might occur, as
- ** wal_autocheckpoint ought to be turned off. */
+ if( pRbu && (
+ pRbu->eStage==RBU_STAGE_OAL
+ || pRbu->eStage==RBU_STAGE_MOVE
+ || pRbu->eStage==RBU_STAGE_DONE
+ )){
+ /* Prevent SQLite from taking a shm-lock on the target file when it
+ ** is supplying heap memory to the upper layer in place of *-shm
+ ** segments. */
if( ofst==WAL_LOCK_CKPT && n==1 ) rc = SQLITE_BUSY;
}else{
int bCapture = 0;
if( pRbu && pRbu->eStage==RBU_STAGE_CAPTURE ){
bCapture = 1;
}
-
if( bCapture==0 || 0==(flags & SQLITE_SHM_UNLOCK) ){
rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags);
if( bCapture && rc==SQLITE_OK ){
- pRbu->mLock |= (1 << ofst);
+ pRbu->mLock |= ((1<<n) - 1) << ofst;
}
}
}
@@ -200842,28 +205065,14 @@ static int rbuVfsOpen(
rbu_file *pDb = rbuFindMaindb(pRbuVfs, zName, 0);
if( pDb ){
if( pDb->pRbu && pDb->pRbu->eStage==RBU_STAGE_OAL ){
- /* This call is to open a *-wal file. Intead, open the *-oal. This
- ** code ensures that the string passed to xOpen() is terminated by a
- ** pair of '\0' bytes in case the VFS attempts to extract a URI
- ** parameter from it. */
- const char *zBase = zName;
- size_t nCopy;
- char *zCopy;
+ /* This call is to open a *-wal file. Intead, open the *-oal. */
+ size_t nOpen;
if( rbuIsVacuum(pDb->pRbu) ){
- zBase = sqlite3_db_filename(pDb->pRbu->dbRbu, "main");
- zBase = sqlite3_filename_wal(zBase);
- }
- nCopy = strlen(zBase);
- zCopy = sqlite3_malloc64(nCopy+2);
- if( zCopy ){
- memcpy(zCopy, zBase, nCopy);
- zCopy[nCopy-3] = 'o';
- zCopy[nCopy] = '\0';
- zCopy[nCopy+1] = '\0';
- zOpen = (const char*)(pFd->zDel = zCopy);
- }else{
- rc = SQLITE_NOMEM;
+ zOpen = sqlite3_db_filename(pDb->pRbu->dbRbu, "main");
+ zOpen = sqlite3_filename_wal(zOpen);
}
+ nOpen = strlen(zOpen);
+ ((char*)zOpen)[nOpen-3] = 'o';
pFd->pRbu = pDb->pRbu;
}
pDb->pWalFd = pFd;
@@ -202476,12 +206685,15 @@ struct SessionHook {
struct sqlite3_session {
sqlite3 *db; /* Database handle session is attached to */
char *zDb; /* Name of database session is attached to */
+ int bEnableSize; /* True if changeset_size() enabled */
int bEnable; /* True if currently recording */
int bIndirect; /* True if all changes are indirect */
int bAutoAttach; /* True to auto-attach tables */
int rc; /* Non-zero if an error has occurred */
void *pFilterCtx; /* First argument to pass to xTableFilter */
int (*xTableFilter)(void *pCtx, const char *zTab);
+ i64 nMalloc; /* Number of bytes of data allocated */
+ i64 nMaxChangesetSize;
sqlite3_value *pZeroBlob; /* Value containing X'' */
sqlite3_session *pNext; /* Next session object on same db. */
SessionTable *pTable; /* List of attached tables */
@@ -202524,6 +206736,7 @@ struct sqlite3_changeset_iter {
SessionBuffer tblhdr; /* Buffer to hold apValue/zTab/abPK/ */
int bPatchset; /* True if this is a patchset */
int bInvert; /* True to invert changeset */
+ int bSkipEmpty; /* Skip noop UPDATE changes */
int rc; /* Iterator error code */
sqlite3_stmt *pConflict; /* Points to conflicting row, if any */
char *zTab; /* Current table */
@@ -202723,8 +206936,9 @@ struct SessionTable {
** this structure stored in a SessionTable.aChange[] hash table.
*/
struct SessionChange {
- int op; /* One of UPDATE, DELETE, INSERT */
- int bIndirect; /* True if this change is "indirect" */
+ u8 op; /* One of UPDATE, DELETE, INSERT */
+ u8 bIndirect; /* True if this change is "indirect" */
+ int nMaxSize; /* Max size of eventual changeset record */
int nRecord; /* Number of bytes in buffer aRecord[] */
u8 *aRecord; /* Buffer containing old.* record */
SessionChange *pNext; /* For hash-table collisions */
@@ -202865,6 +207079,26 @@ static int sessionSerializeValue(
return SQLITE_OK;
}
+/*
+** Allocate and return a pointer to a buffer nByte bytes in size. If
+** pSession is not NULL, increase the sqlite3_session.nMalloc variable
+** by the number of bytes allocated.
+*/
+static void *sessionMalloc64(sqlite3_session *pSession, i64 nByte){
+ void *pRet = sqlite3_malloc64(nByte);
+ if( pSession ) pSession->nMalloc += sqlite3_msize(pRet);
+ return pRet;
+}
+
+/*
+** Free buffer pFree, which must have been allocated by an earlier
+** call to sessionMalloc64(). If pSession is not NULL, decrease the
+** sqlite3_session.nMalloc counter by the number of bytes freed.
+*/
+static void sessionFree(sqlite3_session *pSession, void *pFree){
+ if( pSession ) pSession->nMalloc -= sqlite3_msize(pFree);
+ sqlite3_free(pFree);
+}
/*
** This macro is used to calculate hash key values for data structures. In
@@ -203332,13 +207566,19 @@ static int sessionPreupdateEqual(
** Growing the hash table in this case is a performance optimization only,
** it is not required for correct operation.
*/
-static int sessionGrowHash(int bPatchset, SessionTable *pTab){
+static int sessionGrowHash(
+ sqlite3_session *pSession, /* For memory accounting. May be NULL */
+ int bPatchset,
+ SessionTable *pTab
+){
if( pTab->nChange==0 || pTab->nEntry>=(pTab->nChange/2) ){
int i;
SessionChange **apNew;
sqlite3_int64 nNew = 2*(sqlite3_int64)(pTab->nChange ? pTab->nChange : 128);
- apNew = (SessionChange **)sqlite3_malloc64(sizeof(SessionChange *) * nNew);
+ apNew = (SessionChange**)sessionMalloc64(
+ pSession, sizeof(SessionChange*) * nNew
+ );
if( apNew==0 ){
if( pTab->nChange==0 ){
return SQLITE_ERROR;
@@ -203359,7 +207599,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){
}
}
- sqlite3_free(pTab->apChange);
+ sessionFree(pSession, pTab->apChange);
pTab->nChange = nNew;
pTab->apChange = apNew;
}
@@ -203393,6 +207633,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){
** be freed using sqlite3_free() by the caller
*/
static int sessionTableInfo(
+ sqlite3_session *pSession, /* For memory accounting. May be NULL */
sqlite3 *db, /* Database connection */
const char *zDb, /* Name of attached database (e.g. "main") */
const char *zThis, /* Table name */
@@ -203447,7 +207688,7 @@ static int sessionTableInfo(
if( rc==SQLITE_OK ){
nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
- pAlloc = sqlite3_malloc64(nByte);
+ pAlloc = sessionMalloc64(pSession, nByte);
if( pAlloc==0 ){
rc = SQLITE_NOMEM;
}
@@ -203490,7 +207731,7 @@ static int sessionTableInfo(
*pabPK = 0;
*pnCol = 0;
if( pzTab ) *pzTab = 0;
- sqlite3_free(azCol);
+ sessionFree(pSession, azCol);
}
sqlite3_finalize(pStmt);
return rc;
@@ -203512,7 +207753,7 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
if( pTab->nCol==0 ){
u8 *abPK;
assert( pTab->azCol==0 || pTab->abPK==0 );
- pSession->rc = sessionTableInfo(pSession->db, pSession->zDb,
+ pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb,
pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK
);
if( pSession->rc==SQLITE_OK ){
@@ -203526,6 +207767,12 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){
pTab->bStat1 = 1;
}
+
+ if( pSession->bEnableSize ){
+ pSession->nMaxChangesetSize += (
+ 1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1
+ );
+ }
}
}
return (pSession->rc || pTab->abPK==0);
@@ -203571,6 +207818,103 @@ static int sessionStat1Depth(void *pCtx){
return p->hook.xDepth(p->hook.pCtx);
}
+static int sessionUpdateMaxSize(
+ int op,
+ sqlite3_session *pSession, /* Session object pTab is attached to */
+ SessionTable *pTab, /* Table that change applies to */
+ SessionChange *pC /* Update pC->nMaxSize */
+){
+ i64 nNew = 2;
+ if( pC->op==SQLITE_INSERT ){
+ if( op!=SQLITE_DELETE ){
+ int ii;
+ for(ii=0; ii<pTab->nCol; ii++){
+ sqlite3_value *p = 0;
+ pSession->hook.xNew(pSession->hook.pCtx, ii, &p);
+ sessionSerializeValue(0, p, &nNew);
+ }
+ }
+ }else if( op==SQLITE_DELETE ){
+ nNew += pC->nRecord;
+ if( sqlite3_preupdate_blobwrite(pSession->db)>=0 ){
+ nNew += pC->nRecord;
+ }
+ }else{
+ int ii;
+ u8 *pCsr = pC->aRecord;
+ for(ii=0; ii<pTab->nCol; ii++){
+ int bChanged = 1;
+ int nOld = 0;
+ int eType;
+ sqlite3_value *p = 0;
+ pSession->hook.xNew(pSession->hook.pCtx, ii, &p);
+ if( p==0 ){
+ return SQLITE_NOMEM;
+ }
+
+ eType = *pCsr++;
+ switch( eType ){
+ case SQLITE_NULL:
+ bChanged = sqlite3_value_type(p)!=SQLITE_NULL;
+ break;
+
+ case SQLITE_FLOAT:
+ case SQLITE_INTEGER: {
+ if( eType==sqlite3_value_type(p) ){
+ sqlite3_int64 iVal = sessionGetI64(pCsr);
+ if( eType==SQLITE_INTEGER ){
+ bChanged = (iVal!=sqlite3_value_int64(p));
+ }else{
+ double dVal;
+ memcpy(&dVal, &iVal, 8);
+ bChanged = (dVal!=sqlite3_value_double(p));
+ }
+ }
+ nOld = 8;
+ pCsr += 8;
+ break;
+ }
+
+ default: {
+ int nByte;
+ nOld = sessionVarintGet(pCsr, &nByte);
+ pCsr += nOld;
+ nOld += nByte;
+ assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
+ if( eType==sqlite3_value_type(p)
+ && nByte==sqlite3_value_bytes(p)
+ && (nByte==0 || 0==memcmp(pCsr, sqlite3_value_blob(p), nByte))
+ ){
+ bChanged = 0;
+ }
+ pCsr += nByte;
+ break;
+ }
+ }
+
+ if( bChanged && pTab->abPK[ii] ){
+ nNew = pC->nRecord + 2;
+ break;
+ }
+
+ if( bChanged ){
+ nNew += 1 + nOld;
+ sessionSerializeValue(0, p, &nNew);
+ }else if( pTab->abPK[ii] ){
+ nNew += 2 + nOld;
+ }else{
+ nNew += 2;
+ }
+ }
+ }
+
+ if( nNew>pC->nMaxSize ){
+ int nIncr = nNew - pC->nMaxSize;
+ pC->nMaxSize = nNew;
+ pSession->nMaxChangesetSize += nIncr;
+ }
+ return SQLITE_OK;
+}
/*
** This function is only called from with a pre-update-hook reporting a
@@ -203603,7 +207947,7 @@ static void sessionPreupdateOneChange(
}
/* Grow the hash table if required */
- if( sessionGrowHash(0, pTab) ){
+ if( sessionGrowHash(pSession, 0, pTab) ){
pSession->rc = SQLITE_NOMEM;
return;
}
@@ -203644,7 +207988,6 @@ static void sessionPreupdateOneChange(
/* Create a new change object containing all the old values (if
** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
** values (if this is an INSERT). */
- SessionChange *pChange; /* New change object */
sqlite3_int64 nByte; /* Number of bytes to allocate */
int i; /* Used to iterate through columns */
@@ -203670,13 +208013,13 @@ static void sessionPreupdateOneChange(
}
/* Allocate the change object */
- pChange = (SessionChange *)sqlite3_malloc64(nByte);
- if( !pChange ){
+ pC = (SessionChange *)sessionMalloc64(pSession, nByte);
+ if( !pC ){
rc = SQLITE_NOMEM;
goto error_out;
}else{
- memset(pChange, 0, sizeof(SessionChange));
- pChange->aRecord = (u8 *)&pChange[1];
+ memset(pC, 0, sizeof(SessionChange));
+ pC->aRecord = (u8 *)&pC[1];
}
/* Populate the change object. None of the preupdate_old(),
@@ -203691,17 +208034,17 @@ static void sessionPreupdateOneChange(
}else if( pTab->abPK[i] ){
pSession->hook.xNew(pSession->hook.pCtx, i, &p);
}
- sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
+ sessionSerializeValue(&pC->aRecord[nByte], p, &nByte);
}
/* Add the change to the hash-table */
if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){
- pChange->bIndirect = 1;
+ pC->bIndirect = 1;
}
- pChange->nRecord = nByte;
- pChange->op = op;
- pChange->pNext = pTab->apChange[iHash];
- pTab->apChange[iHash] = pChange;
+ pC->nRecord = nByte;
+ pC->op = op;
+ pC->pNext = pTab->apChange[iHash];
+ pTab->apChange[iHash] = pC;
}else if( pC->bIndirect ){
/* If the existing change is considered "indirect", but this current
@@ -203712,8 +208055,14 @@ static void sessionPreupdateOneChange(
pC->bIndirect = 0;
}
}
+
+ assert( rc==SQLITE_OK );
+ if( pSession->bEnableSize ){
+ rc = sessionUpdateMaxSize(op, pSession, pTab, pC);
+ }
}
+
/* If an error has occurred, mark the session object as failed. */
error_out:
if( pTab->bStat1 ){
@@ -204043,7 +208392,7 @@ SQLITE_API int sqlite3session_diff(
int nCol; /* Columns in zFrom.zTbl */
u8 *abPK;
const char **azCol = 0;
- rc = sessionTableInfo(db, zFrom, zTbl, &nCol, 0, &azCol, &abPK);
+ rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK);
if( rc==SQLITE_OK ){
if( pTo->nCol!=nCol ){
bMismatch = 1;
@@ -204141,7 +208490,7 @@ SQLITE_API int sqlite3session_create(
** Free the list of table objects passed as the first argument. The contents
** of the changed-rows hash tables are also deleted.
*/
-static void sessionDeleteTable(SessionTable *pList){
+static void sessionDeleteTable(sqlite3_session *pSession, SessionTable *pList){
SessionTable *pNext;
SessionTable *pTab;
@@ -204153,12 +208502,12 @@ static void sessionDeleteTable(SessionTable *pList){
SessionChange *pNextChange;
for(p=pTab->apChange[i]; p; p=pNextChange){
pNextChange = p->pNext;
- sqlite3_free(p);
+ sessionFree(pSession, p);
}
}
- sqlite3_free((char*)pTab->azCol); /* cast works around VC++ bug */
- sqlite3_free(pTab->apChange);
- sqlite3_free(pTab);
+ sessionFree(pSession, (char*)pTab->azCol); /* cast works around VC++ bug */
+ sessionFree(pSession, pTab->apChange);
+ sessionFree(pSession, pTab);
}
}
@@ -204186,9 +208535,11 @@ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession){
/* Delete all attached table objects. And the contents of their
** associated hash-tables. */
- sessionDeleteTable(pSession->pTable);
+ sessionDeleteTable(pSession, pSession->pTable);
- /* Free the session object itself. */
+ /* Assert that all allocations have been freed and then free the
+ ** session object itself. */
+ assert( pSession->nMalloc==0 );
sqlite3_free(pSession);
}
@@ -204235,7 +208586,8 @@ SQLITE_API int sqlite3session_attach(
if( !pTab ){
/* Allocate new SessionTable object. */
- pTab = (SessionTable *)sqlite3_malloc64(sizeof(SessionTable) + nName + 1);
+ int nByte = sizeof(SessionTable) + nName + 1;
+ pTab = (SessionTable*)sessionMalloc64(pSession, nByte);
if( !pTab ){
rc = SQLITE_NOMEM;
}else{
@@ -204265,13 +208617,29 @@ SQLITE_API int sqlite3session_attach(
** If successful, return zero. Otherwise, if an OOM condition is encountered,
** set *pRc to SQLITE_NOMEM and return non-zero.
*/
-static int sessionBufferGrow(SessionBuffer *p, size_t nByte, int *pRc){
- if( *pRc==SQLITE_OK && (size_t)(p->nAlloc-p->nBuf)<nByte ){
+static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){
+#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1)
+ i64 nReq = p->nBuf + nByte;
+ if( *pRc==SQLITE_OK && nReq>p->nAlloc ){
u8 *aNew;
i64 nNew = p->nAlloc ? p->nAlloc : 128;
+
do {
nNew = nNew*2;
- }while( (size_t)(nNew-p->nBuf)<nByte );
+ }while( nNew<nReq );
+
+ /* The value of SESSION_MAX_BUFFER_SZ is copied from the implementation
+ ** of sqlite3_realloc64(). Allocations greater than this size in bytes
+ ** always fail. It is used here to ensure that this routine can always
+ ** allocate up to this limit - instead of up to the largest power of
+ ** two smaller than the limit. */
+ if( nNew>SESSION_MAX_BUFFER_SZ ){
+ nNew = SESSION_MAX_BUFFER_SZ;
+ if( nNew<nReq ){
+ *pRc = SQLITE_NOMEM;
+ return 1;
+ }
+ }
aNew = (u8 *)sqlite3_realloc64(p->aBuf, nNew);
if( 0==aNew ){
@@ -204832,7 +209200,7 @@ static int sessionGenerateChangeset(
int nNoop; /* Size of buffer after writing tbl header */
/* Check the table schema is still Ok. */
- rc = sessionTableInfo(db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK);
+ rc = sessionTableInfo(0, db, pSession->zDb, zName, &nCol, 0,&azCol,&abPK);
if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){
rc = SQLITE_SCHEMA;
}
@@ -204922,7 +209290,11 @@ SQLITE_API int sqlite3session_changeset(
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
){
- return sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset);
+ int rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset,ppChangeset);
+ assert( rc || pnChangeset==0
+ || pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize
+ );
+ return rc;
}
/*
@@ -205008,6 +209380,46 @@ SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession){
}
/*
+** Return the amount of heap memory in use.
+*/
+SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession){
+ return pSession->nMalloc;
+}
+
+/*
+** Configure the session object passed as the first argument.
+*/
+SQLITE_API int sqlite3session_object_config(sqlite3_session *pSession, int op, void *pArg){
+ int rc = SQLITE_OK;
+ switch( op ){
+ case SQLITE_SESSION_OBJCONFIG_SIZE: {
+ int iArg = *(int*)pArg;
+ if( iArg>=0 ){
+ if( pSession->pTable ){
+ rc = SQLITE_MISUSE;
+ }else{
+ pSession->bEnableSize = (iArg!=0);
+ }
+ }
+ *(int*)pArg = pSession->bEnableSize;
+ break;
+ }
+
+ default:
+ rc = SQLITE_MISUSE;
+ }
+
+ return rc;
+}
+
+/*
+** Return the maximum size of sqlite3session_changeset() output.
+*/
+SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession){
+ return pSession->nMaxChangesetSize;
+}
+
+/*
** Do the work for either sqlite3changeset_start() or start_strm().
*/
static int sessionChangesetStart(
@@ -205016,7 +209428,8 @@ static int sessionChangesetStart(
void *pIn,
int nChangeset, /* Size of buffer pChangeset in bytes */
void *pChangeset, /* Pointer to buffer containing changeset */
- int bInvert /* True to invert changeset */
+ int bInvert, /* True to invert changeset */
+ int bSkipEmpty /* True to skip empty UPDATE changes */
){
sqlite3_changeset_iter *pRet; /* Iterator to return */
int nByte; /* Number of bytes to allocate for iterator */
@@ -205037,6 +209450,7 @@ static int sessionChangesetStart(
pRet->in.pIn = pIn;
pRet->in.bEof = (xInput ? 0 : 1);
pRet->bInvert = bInvert;
+ pRet->bSkipEmpty = bSkipEmpty;
/* Populate the output variable and return success. */
*pp = pRet;
@@ -205051,7 +209465,7 @@ SQLITE_API int sqlite3changeset_start(
int nChangeset, /* Size of buffer pChangeset in bytes */
void *pChangeset /* Pointer to buffer containing changeset */
){
- return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0);
+ return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0, 0);
}
SQLITE_API int sqlite3changeset_start_v2(
sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
@@ -205060,7 +209474,7 @@ SQLITE_API int sqlite3changeset_start_v2(
int flags
){
int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT);
- return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert);
+ return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert, 0);
}
/*
@@ -205071,7 +209485,7 @@ SQLITE_API int sqlite3changeset_start_strm(
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
){
- return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0);
+ return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0, 0);
}
SQLITE_API int sqlite3changeset_start_v2_strm(
sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
@@ -205080,7 +209494,7 @@ SQLITE_API int sqlite3changeset_start_v2_strm(
int flags
){
int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT);
- return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert);
+ return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert, 0);
}
/*
@@ -205206,11 +209620,14 @@ static int sessionReadRecord(
SessionInput *pIn, /* Input data */
int nCol, /* Number of values in record */
u8 *abPK, /* Array of primary key flags, or NULL */
- sqlite3_value **apOut /* Write values to this array */
+ sqlite3_value **apOut, /* Write values to this array */
+ int *pbEmpty
){
int i; /* Used to iterate through columns */
int rc = SQLITE_OK;
+ assert( pbEmpty==0 || *pbEmpty==0 );
+ if( pbEmpty ) *pbEmpty = 1;
for(i=0; i<nCol && rc==SQLITE_OK; i++){
int eType = 0; /* Type of value (SQLITE_NULL, TEXT etc.) */
if( abPK && abPK[i]==0 ) continue;
@@ -205222,6 +209639,7 @@ static int sessionReadRecord(
eType = pIn->aData[pIn->iNext++];
assert( apOut[i]==0 );
if( eType ){
+ if( pbEmpty ) *pbEmpty = 0;
apOut[i] = sqlite3ValueNew(0);
if( !apOut[i] ) rc = SQLITE_NOMEM;
}
@@ -205401,31 +209819,27 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
}
/*
-** Advance the changeset iterator to the next change.
+** Advance the changeset iterator to the next change. The differences between
+** this function and sessionChangesetNext() are that
**
-** If both paRec and pnRec are NULL, then this function works like the public
-** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the
-** sqlite3changeset_new() and old() APIs may be used to query for values.
+** * If pbEmpty is not NULL and the change is a no-op UPDATE (an UPDATE
+** that modifies no columns), this function sets (*pbEmpty) to 1.
**
-** Otherwise, if paRec and pnRec are not NULL, then a pointer to the change
-** record is written to *paRec before returning and the number of bytes in
-** the record to *pnRec.
-**
-** Either way, this function returns SQLITE_ROW if the iterator is
-** successfully advanced to the next change in the changeset, an SQLite
-** error code if an error occurs, or SQLITE_DONE if there are no further
-** changes in the changeset.
+** * If the iterator is configured to skip no-op UPDATEs,
+** sessionChangesetNext() does that. This function does not.
*/
-static int sessionChangesetNext(
+static int sessionChangesetNextOne(
sqlite3_changeset_iter *p, /* Changeset iterator */
u8 **paRec, /* If non-NULL, store record pointer here */
int *pnRec, /* If non-NULL, store size of record here */
- int *pbNew /* If non-NULL, true if new table */
+ int *pbNew, /* If non-NULL, true if new table */
+ int *pbEmpty
){
int i;
u8 op;
assert( (paRec==0 && pnRec==0) || (paRec && pnRec) );
+ assert( pbEmpty==0 || *pbEmpty==0 );
/* If the iterator is in the error-state, return immediately. */
if( p->rc!=SQLITE_OK ) return p->rc;
@@ -205498,13 +209912,13 @@ static int sessionChangesetNext(
/* If this is an UPDATE or DELETE, read the old.* record. */
if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){
u8 *abPK = p->bPatchset ? p->abPK : 0;
- p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld);
+ p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld, 0);
if( p->rc!=SQLITE_OK ) return p->rc;
}
/* If this is an INSERT or UPDATE, read the new.* record. */
if( p->op!=SQLITE_DELETE ){
- p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew);
+ p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew, pbEmpty);
if( p->rc!=SQLITE_OK ) return p->rc;
}
@@ -205532,6 +209946,37 @@ static int sessionChangesetNext(
}
/*
+** Advance the changeset iterator to the next change.
+**
+** If both paRec and pnRec are NULL, then this function works like the public
+** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the
+** sqlite3changeset_new() and old() APIs may be used to query for values.
+**
+** Otherwise, if paRec and pnRec are not NULL, then a pointer to the change
+** record is written to *paRec before returning and the number of bytes in
+** the record to *pnRec.
+**
+** Either way, this function returns SQLITE_ROW if the iterator is
+** successfully advanced to the next change in the changeset, an SQLite
+** error code if an error occurs, or SQLITE_DONE if there are no further
+** changes in the changeset.
+*/
+static int sessionChangesetNext(
+ sqlite3_changeset_iter *p, /* Changeset iterator */
+ u8 **paRec, /* If non-NULL, store record pointer here */
+ int *pnRec, /* If non-NULL, store size of record here */
+ int *pbNew /* If non-NULL, true if new table */
+){
+ int bEmpty;
+ int rc;
+ do {
+ bEmpty = 0;
+ rc = sessionChangesetNextOne(p, paRec, pnRec, pbNew, &bEmpty);
+ }while( rc==SQLITE_ROW && p->bSkipEmpty && bEmpty);
+ return rc;
+}
+
+/*
** Advance an iterator created by sqlite3changeset_start() to the next
** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE
** or SQLITE_CORRUPT.
@@ -205803,9 +210248,9 @@ static int sessionChangesetInvert(
/* Read the old.* and new.* records for the update change. */
pInput->iNext += 2;
- rc = sessionReadRecord(pInput, nCol, 0, &apVal[0]);
+ rc = sessionReadRecord(pInput, nCol, 0, &apVal[0], 0);
if( rc==SQLITE_OK ){
- rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol]);
+ rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol], 0);
}
/* Write the new old.* record. Consists of the PK columns from the
@@ -205906,16 +210351,25 @@ SQLITE_API int sqlite3changeset_invert_strm(
return rc;
}
+
+typedef struct SessionUpdate SessionUpdate;
+struct SessionUpdate {
+ sqlite3_stmt *pStmt;
+ u32 *aMask;
+ SessionUpdate *pNext;
+};
+
typedef struct SessionApplyCtx SessionApplyCtx;
struct SessionApplyCtx {
sqlite3 *db;
sqlite3_stmt *pDelete; /* DELETE statement */
- sqlite3_stmt *pUpdate; /* UPDATE statement */
sqlite3_stmt *pInsert; /* INSERT statement */
sqlite3_stmt *pSelect; /* SELECT statement */
int nCol; /* Size of azCol[] and abPK[] arrays */
const char **azCol; /* Array of column names */
u8 *abPK; /* Boolean array - true if column is in PK */
+ u32 *aUpdateMask; /* Used by sessionUpdateFind */
+ SessionUpdate *pUp;
int bStat1; /* True if table is sqlite_stat1 */
int bDeferConstraints; /* True to defer constraints */
int bInvertConstraints; /* Invert when iterating constraints buffer */
@@ -205925,6 +210379,167 @@ struct SessionApplyCtx {
u8 bRebase; /* True to collect rebase information */
};
+/* Number of prepared UPDATE statements to cache. */
+#define SESSION_UPDATE_CACHE_SZ 12
+
+/*
+** Find a prepared UPDATE statement suitable for the UPDATE step currently
+** being visited by the iterator. The UPDATE is of the form:
+**
+** UPDATE tbl SET col = ?, col2 = ? WHERE pk1 IS ? AND pk2 IS ?
+*/
+static int sessionUpdateFind(
+ sqlite3_changeset_iter *pIter,
+ SessionApplyCtx *p,
+ int bPatchset,
+ sqlite3_stmt **ppStmt
+){
+ int rc = SQLITE_OK;
+ SessionUpdate *pUp = 0;
+ int nCol = pIter->nCol;
+ int nU32 = (pIter->nCol+33)/32;
+ int ii;
+
+ if( p->aUpdateMask==0 ){
+ p->aUpdateMask = sqlite3_malloc(nU32*sizeof(u32));
+ if( p->aUpdateMask==0 ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ memset(p->aUpdateMask, 0, nU32*sizeof(u32));
+ rc = SQLITE_CORRUPT;
+ for(ii=0; ii<pIter->nCol; ii++){
+ if( sessionChangesetNew(pIter, ii) ){
+ p->aUpdateMask[ii/32] |= (1<<(ii%32));
+ rc = SQLITE_OK;
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ if( bPatchset ) p->aUpdateMask[nCol/32] |= (1<<(nCol%32));
+
+ if( p->pUp ){
+ int nUp = 0;
+ SessionUpdate **pp = &p->pUp;
+ while( 1 ){
+ nUp++;
+ if( 0==memcmp(p->aUpdateMask, (*pp)->aMask, nU32*sizeof(u32)) ){
+ pUp = *pp;
+ *pp = pUp->pNext;
+ pUp->pNext = p->pUp;
+ p->pUp = pUp;
+ break;
+ }
+
+ if( (*pp)->pNext ){
+ pp = &(*pp)->pNext;
+ }else{
+ if( nUp>=SESSION_UPDATE_CACHE_SZ ){
+ sqlite3_finalize((*pp)->pStmt);
+ sqlite3_free(*pp);
+ *pp = 0;
+ }
+ break;
+ }
+ }
+ }
+
+ if( pUp==0 ){
+ int nByte = sizeof(SessionUpdate) * nU32*sizeof(u32);
+ int bStat1 = (sqlite3_stricmp(pIter->zTab, "sqlite_stat1")==0);
+ pUp = (SessionUpdate*)sqlite3_malloc(nByte);
+ if( pUp==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ const char *zSep = "";
+ SessionBuffer buf;
+
+ memset(&buf, 0, sizeof(buf));
+ pUp->aMask = (u32*)&pUp[1];
+ memcpy(pUp->aMask, p->aUpdateMask, nU32*sizeof(u32));
+
+ sessionAppendStr(&buf, "UPDATE main.", &rc);
+ sessionAppendIdent(&buf, pIter->zTab, &rc);
+ sessionAppendStr(&buf, " SET ", &rc);
+
+ /* Create the assignments part of the UPDATE */
+ for(ii=0; ii<pIter->nCol; ii++){
+ if( p->abPK[ii]==0 && sessionChangesetNew(pIter, ii) ){
+ sessionAppendStr(&buf, zSep, &rc);
+ sessionAppendIdent(&buf, p->azCol[ii], &rc);
+ sessionAppendStr(&buf, " = ?", &rc);
+ sessionAppendInteger(&buf, ii*2+1, &rc);
+ zSep = ", ";
+ }
+ }
+
+ /* Create the WHERE clause part of the UPDATE */
+ zSep = "";
+ sessionAppendStr(&buf, " WHERE ", &rc);
+ for(ii=0; ii<pIter->nCol; ii++){
+ if( p->abPK[ii] || (bPatchset==0 && sessionChangesetOld(pIter, ii)) ){
+ sessionAppendStr(&buf, zSep, &rc);
+ if( bStat1 && ii==1 ){
+ assert( sqlite3_stricmp(p->azCol[ii], "idx")==0 );
+ sessionAppendStr(&buf,
+ "idx IS CASE "
+ "WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL "
+ "ELSE ?4 END ", &rc
+ );
+ }else{
+ sessionAppendIdent(&buf, p->azCol[ii], &rc);
+ sessionAppendStr(&buf, " IS ?", &rc);
+ sessionAppendInteger(&buf, ii*2+2, &rc);
+ }
+ zSep = " AND ";
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ char *zSql = (char*)buf.aBuf;
+ rc = sqlite3_prepare_v2(p->db, zSql, buf.nBuf, &pUp->pStmt, 0);
+ }
+
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(pUp);
+ pUp = 0;
+ }else{
+ pUp->pNext = p->pUp;
+ p->pUp = pUp;
+ }
+ sqlite3_free(buf.aBuf);
+ }
+ }
+ }
+
+ assert( (rc==SQLITE_OK)==(pUp!=0) );
+ if( pUp ){
+ *ppStmt = pUp->pStmt;
+ }else{
+ *ppStmt = 0;
+ }
+ return rc;
+}
+
+/*
+** Free all cached UPDATE statements.
+*/
+static void sessionUpdateFree(SessionApplyCtx *p){
+ SessionUpdate *pUp;
+ SessionUpdate *pNext;
+ for(pUp=p->pUp; pUp; pUp=pNext){
+ pNext = pUp->pNext;
+ sqlite3_finalize(pUp->pStmt);
+ sqlite3_free(pUp);
+ }
+ p->pUp = 0;
+ sqlite3_free(p->aUpdateMask);
+ p->aUpdateMask = 0;
+}
+
/*
** Formulate a statement to DELETE a row from database db. Assuming a table
** structure like this:
@@ -205995,103 +210610,6 @@ static int sessionDeleteRow(
}
/*
-** Formulate and prepare a statement to UPDATE a row from database db.
-** Assuming a table structure like this:
-**
-** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
-**
-** The UPDATE statement looks like this:
-**
-** UPDATE x SET
-** a = CASE WHEN ?2 THEN ?3 ELSE a END,
-** b = CASE WHEN ?5 THEN ?6 ELSE b END,
-** c = CASE WHEN ?8 THEN ?9 ELSE c END,
-** d = CASE WHEN ?11 THEN ?12 ELSE d END
-** WHERE a = ?1 AND c = ?7 AND (?13 OR
-** (?5==0 OR b IS ?4) AND (?11==0 OR d IS ?10) AND
-** )
-**
-** For each column in the table, there are three variables to bind:
-**
-** ?(i*3+1) The old.* value of the column, if any.
-** ?(i*3+2) A boolean flag indicating that the value is being modified.
-** ?(i*3+3) The new.* value of the column, if any.
-**
-** Also, a boolean flag that, if set to true, causes the statement to update
-** a row even if the non-PK values do not match. This is required if the
-** conflict-handler is invoked with CHANGESET_DATA and returns
-** CHANGESET_REPLACE. This is variable "?(nCol*3+1)".
-**
-** If successful, SQLITE_OK is returned and SessionApplyCtx.pUpdate is left
-** pointing to the prepared version of the SQL statement.
-*/
-static int sessionUpdateRow(
- sqlite3 *db, /* Database handle */
- const char *zTab, /* Table name */
- SessionApplyCtx *p /* Session changeset-apply context */
-){
- int rc = SQLITE_OK;
- int i;
- const char *zSep = "";
- SessionBuffer buf = {0, 0, 0};
-
- /* Append "UPDATE tbl SET " */
- sessionAppendStr(&buf, "UPDATE main.", &rc);
- sessionAppendIdent(&buf, zTab, &rc);
- sessionAppendStr(&buf, " SET ", &rc);
-
- /* Append the assignments */
- for(i=0; i<p->nCol; i++){
- sessionAppendStr(&buf, zSep, &rc);
- sessionAppendIdent(&buf, p->azCol[i], &rc);
- sessionAppendStr(&buf, " = CASE WHEN ?", &rc);
- sessionAppendInteger(&buf, i*3+2, &rc);
- sessionAppendStr(&buf, " THEN ?", &rc);
- sessionAppendInteger(&buf, i*3+3, &rc);
- sessionAppendStr(&buf, " ELSE ", &rc);
- sessionAppendIdent(&buf, p->azCol[i], &rc);
- sessionAppendStr(&buf, " END", &rc);
- zSep = ", ";
- }
-
- /* Append the PK part of the WHERE clause */
- sessionAppendStr(&buf, " WHERE ", &rc);
- for(i=0; i<p->nCol; i++){
- if( p->abPK[i] ){
- sessionAppendIdent(&buf, p->azCol[i], &rc);
- sessionAppendStr(&buf, " = ?", &rc);
- sessionAppendInteger(&buf, i*3+1, &rc);
- sessionAppendStr(&buf, " AND ", &rc);
- }
- }
-
- /* Append the non-PK part of the WHERE clause */
- sessionAppendStr(&buf, " (?", &rc);
- sessionAppendInteger(&buf, p->nCol*3+1, &rc);
- sessionAppendStr(&buf, " OR 1", &rc);
- for(i=0; i<p->nCol; i++){
- if( !p->abPK[i] ){
- sessionAppendStr(&buf, " AND (?", &rc);
- sessionAppendInteger(&buf, i*3+2, &rc);
- sessionAppendStr(&buf, "=0 OR ", &rc);
- sessionAppendIdent(&buf, p->azCol[i], &rc);
- sessionAppendStr(&buf, " IS ?", &rc);
- sessionAppendInteger(&buf, i*3+1, &rc);
- sessionAppendStr(&buf, ")", &rc);
- }
- }
- sessionAppendStr(&buf, ")", &rc);
-
- if( rc==SQLITE_OK ){
- rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0);
- }
- sqlite3_free(buf.aBuf);
-
- return rc;
-}
-
-
-/*
** Formulate and prepare an SQL statement to query table zTab by primary
** key. Assuming the following table structure:
**
@@ -206172,17 +210690,6 @@ static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){
);
}
if( rc==SQLITE_OK ){
- rc = sessionPrepare(db, &p->pUpdate,
- "UPDATE main.sqlite_stat1 SET "
- "tbl = CASE WHEN ?2 THEN ?3 ELSE tbl END, "
- "idx = CASE WHEN ?5 THEN ?6 ELSE idx END, "
- "stat = CASE WHEN ?8 THEN ?9 ELSE stat END "
- "WHERE tbl=?1 AND idx IS "
- "CASE WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL ELSE ?4 END "
- "AND (?10 OR ?8=0 OR stat IS ?7)"
- );
- }
- if( rc==SQLITE_OK ){
rc = sessionPrepare(db, &p->pDelete,
"DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS "
"CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END "
@@ -206498,7 +211005,7 @@ static int sessionApplyOneOp(
int nCol;
int rc = SQLITE_OK;
- assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect );
+ assert( p->pDelete && p->pInsert && p->pSelect );
assert( p->azCol && p->abPK );
assert( !pbReplace || *pbReplace==0 );
@@ -206538,29 +211045,28 @@ static int sessionApplyOneOp(
}else if( op==SQLITE_UPDATE ){
int i;
+ sqlite3_stmt *pUp = 0;
+ int bPatchset = (pbRetry==0 || pIter->bPatchset);
+
+ rc = sessionUpdateFind(pIter, p, bPatchset, &pUp);
/* Bind values to the UPDATE statement. */
for(i=0; rc==SQLITE_OK && i<nCol; i++){
sqlite3_value *pOld = sessionChangesetOld(pIter, i);
sqlite3_value *pNew = sessionChangesetNew(pIter, i);
-
- sqlite3_bind_int(p->pUpdate, i*3+2, !!pNew);
- if( pOld ){
- rc = sessionBindValue(p->pUpdate, i*3+1, pOld);
+ if( p->abPK[i] || (bPatchset==0 && pOld) ){
+ rc = sessionBindValue(pUp, i*2+2, pOld);
}
if( rc==SQLITE_OK && pNew ){
- rc = sessionBindValue(p->pUpdate, i*3+3, pNew);
+ rc = sessionBindValue(pUp, i*2+1, pNew);
}
}
- if( rc==SQLITE_OK ){
- sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0 || pIter->bPatchset);
- }
if( rc!=SQLITE_OK ) return rc;
/* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict,
** the result will be SQLITE_OK with 0 rows modified. */
- sqlite3_step(p->pUpdate);
- rc = sqlite3_reset(p->pUpdate);
+ sqlite3_step(pUp);
+ rc = sqlite3_reset(pUp);
if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
/* A NOTFOUND or DATA error. Search the table to see if it contains
@@ -206692,7 +211198,7 @@ static int sessionRetryConstraints(
memset(&pApply->constraints, 0, sizeof(SessionBuffer));
rc = sessionChangesetStart(
- &pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints
+ &pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints, 1
);
if( rc==SQLITE_OK ){
size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*);
@@ -206783,14 +211289,13 @@ static int sessionChangesetApply(
);
if( rc!=SQLITE_OK ) break;
+ sessionUpdateFree(&sApply);
sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
sqlite3_finalize(sApply.pDelete);
- sqlite3_finalize(sApply.pUpdate);
sqlite3_finalize(sApply.pInsert);
sqlite3_finalize(sApply.pSelect);
sApply.db = db;
sApply.pDelete = 0;
- sApply.pUpdate = 0;
sApply.pInsert = 0;
sApply.pSelect = 0;
sApply.nCol = 0;
@@ -206818,7 +211323,7 @@ static int sessionChangesetApply(
int i;
sqlite3changeset_pk(pIter, &abPK, 0);
- rc = sessionTableInfo(
+ rc = sessionTableInfo(0,
db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK
);
if( rc!=SQLITE_OK ) break;
@@ -206854,11 +211359,10 @@ static int sessionChangesetApply(
}
sApply.bStat1 = 1;
}else{
- if((rc = sessionSelectRow(db, zTab, &sApply))
- || (rc = sessionUpdateRow(db, zTab, &sApply))
- || (rc = sessionDeleteRow(db, zTab, &sApply))
- || (rc = sessionInsertRow(db, zTab, &sApply))
- ){
+ if( (rc = sessionSelectRow(db, zTab, &sApply))
+ || (rc = sessionDeleteRow(db, zTab, &sApply))
+ || (rc = sessionInsertRow(db, zTab, &sApply))
+ ){
break;
}
sApply.bStat1 = 0;
@@ -206917,9 +211421,9 @@ static int sessionChangesetApply(
*pnRebase = sApply.rebase.nBuf;
sApply.rebase.aBuf = 0;
}
+ sessionUpdateFree(&sApply);
sqlite3_finalize(sApply.pInsert);
sqlite3_finalize(sApply.pDelete);
- sqlite3_finalize(sApply.pUpdate);
sqlite3_finalize(sApply.pSelect);
sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
sqlite3_free((char*)sApply.constraints.aBuf);
@@ -206950,8 +211454,8 @@ SQLITE_API int sqlite3changeset_apply_v2(
int flags
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
- int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset,bInverse);
+ int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
+ int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
@@ -207009,7 +211513,7 @@ SQLITE_API int sqlite3changeset_apply_v2_strm(
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
- int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse);
+ int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse, 1);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
@@ -207297,7 +211801,7 @@ static int sessionChangesetToHash(
}
}
- if( sessionGrowHash(pIter->bPatchset, pTab) ){
+ if( sessionGrowHash(0, pIter->bPatchset, pTab) ){
rc = SQLITE_NOMEM;
break;
}
@@ -207483,7 +211987,7 @@ SQLITE_API int sqlite3changegroup_output_strm(
*/
SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){
if( pGrp ){
- sessionDeleteTable(pGrp->pList);
+ sessionDeleteTable(0, pGrp->pList);
sqlite3_free(pGrp);
}
}
@@ -207629,7 +212133,7 @@ static void sessionAppendPartialUpdate(
int n1 = sessionSerialLen(a1);
int n2 = sessionSerialLen(a2);
if( pIter->abPK[i] || a2[0]==0 ){
- if( !pIter->abPK[i] ) bData = 1;
+ if( !pIter->abPK[i] && a1[0] ) bData = 1;
memcpy(pOut, a1, n1);
pOut += n1;
}else if( a2[0]!=0xFF ){
@@ -207884,7 +212388,7 @@ SQLITE_API int sqlite3rebaser_rebase_strm(
*/
SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p){
if( p ){
- sessionDeleteTable(p->grp.pList);
+ sessionDeleteTable(0, p->grp.pList);
sqlite3_free(p);
}
}
@@ -208687,6 +213191,7 @@ struct Fts5Config {
Fts5Tokenizer *pTok;
fts5_tokenizer *pTokApi;
int bLock; /* True when table is preparing statement */
+ int ePattern; /* FTS_PATTERN_XXX constant */
/* Values loaded from the %_config table */
int iCookie; /* Incremented when %_config is modified */
@@ -208707,17 +213212,19 @@ struct Fts5Config {
};
/* Current expected value of %_config table 'version' field */
-#define FTS5_CURRENT_VERSION 4
+#define FTS5_CURRENT_VERSION 4
#define FTS5_CONTENT_NORMAL 0
#define FTS5_CONTENT_NONE 1
#define FTS5_CONTENT_EXTERNAL 2
-#define FTS5_DETAIL_FULL 0
-#define FTS5_DETAIL_NONE 1
-#define FTS5_DETAIL_COLUMNS 2
-
+#define FTS5_DETAIL_FULL 0
+#define FTS5_DETAIL_NONE 1
+#define FTS5_DETAIL_COLUMNS 2
+#define FTS5_PATTERN_NONE 0
+#define FTS5_PATTERN_LIKE 65 /* matches SQLITE_INDEX_CONSTRAINT_LIKE */
+#define FTS5_PATTERN_GLOB 66 /* matches SQLITE_INDEX_CONSTRAINT_GLOB */
static int sqlite3Fts5ConfigParse(
Fts5Global*, sqlite3*, int, const char **, Fts5Config**, char**
@@ -208987,7 +213494,7 @@ static int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8*, int);
/*
** Functions called by the storage module as part of integrity-check.
*/
-static int sqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum);
+static int sqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum, int bUseCksum);
/*
** Called during virtual module initialization to register UDF
@@ -209057,8 +213564,7 @@ static int sqlite3Fts5GetTokenizer(
Fts5Global*,
const char **azArg,
int nArg,
- Fts5Tokenizer**,
- fts5_tokenizer**,
+ Fts5Config*,
char **pzErr
);
@@ -209142,7 +213648,7 @@ static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**);
static int sqlite3Fts5StorageContentInsert(Fts5Storage *p, sqlite3_value**, i64*);
static int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64);
-static int sqlite3Fts5StorageIntegrity(Fts5Storage *p);
+static int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg);
static int sqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, sqlite3_stmt**, char**);
static void sqlite3Fts5StorageStmtRelease(Fts5Storage *p, int eStmt, sqlite3_stmt*);
@@ -209187,11 +213693,19 @@ struct Fts5Token {
/* Parse a MATCH expression. */
static int sqlite3Fts5ExprNew(
Fts5Config *pConfig,
+ int bPhraseToAnd,
int iCol, /* Column on LHS of MATCH operator */
const char *zExpr,
Fts5Expr **ppNew,
char **pzErr
);
+static int sqlite3Fts5ExprPattern(
+ Fts5Config *pConfig,
+ int bGlob,
+ int iCol,
+ const char *zText,
+ Fts5Expr **pp
+);
/*
** for(rc = sqlite3Fts5ExprFirst(pExpr, pIdx, bDesc);
@@ -209300,6 +213814,10 @@ static int sqlite3Fts5AuxInit(fts5_api*);
*/
static int sqlite3Fts5TokenizerInit(fts5_api*);
+static int sqlite3Fts5TokenizerPattern(
+ int (*xCreate)(void*, const char**, int, Fts5Tokenizer**),
+ Fts5Tokenizer *pTok
+);
/*
** End of interface to code in fts5_tokenizer.c.
**************************************************************************/
@@ -209346,6 +213864,8 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*);
#define FTS5_PLUS 14
#define FTS5_STAR 15
+/* This file is automatically generated by Lemon from input grammar
+** source file "fts5parse.y". */
/*
** 2000-05-29
**
@@ -209370,8 +213890,6 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*);
** The following is the concatenation of all %include directives from the
** input grammar file:
*/
-/* #include <stdio.h> */
-/* #include <assert.h> */
/************ Begin %include sections from the grammar ************************/
/* #include "fts5Int.h" */
@@ -209401,11 +213919,26 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*);
#define fts5YYMALLOCARGTYPE u64
/**************** End of %include directives **********************************/
-/* These constants specify the various numeric values for terminal symbols
-** in a format understandable to "makeheaders". This section is blank unless
-** "lemon" is run with the "-m" command-line option.
-***************** Begin makeheaders token definitions *************************/
-/**************** End makeheaders token definitions ***************************/
+/* These constants specify the various numeric values for terminal symbols.
+***************** Begin token definitions *************************************/
+#ifndef FTS5_OR
+#define FTS5_OR 1
+#define FTS5_AND 2
+#define FTS5_NOT 3
+#define FTS5_TERM 4
+#define FTS5_COLON 5
+#define FTS5_MINUS 6
+#define FTS5_LCP 7
+#define FTS5_RCP 8
+#define FTS5_STRING 9
+#define FTS5_LP 10
+#define FTS5_RP 11
+#define FTS5_CARET 12
+#define FTS5_COMMA 13
+#define FTS5_PLUS 14
+#define FTS5_STAR 15
+#endif
+/**************** End token definitions ***************************************/
/* The next sections is a series of control #defines.
** various aspects of the generated parser.
@@ -209688,6 +214221,7 @@ typedef struct fts5yyParser fts5yyParser;
#ifndef NDEBUG
/* #include <stdio.h> */
+/* #include <assert.h> */
static FILE *fts5yyTraceFILE = 0;
static char *fts5yyTracePrompt = 0;
#endif /* NDEBUG */
@@ -210102,7 +214636,7 @@ static fts5YYACTIONTYPE fts5yy_find_shift_action(
#endif /* fts5YYWILDCARD */
return fts5yy_default[stateno];
}else{
- assert( i>=0 && i<sizeof(fts5yy_action)/sizeof(fts5yy_action[0]) );
+ assert( i>=0 && i<(int)(sizeof(fts5yy_action)/sizeof(fts5yy_action[0])) );
return fts5yy_action[i];
}
}while(1);
@@ -210316,54 +214850,6 @@ static fts5YYACTIONTYPE fts5yy_reduce(
(void)fts5yyLookahead;
(void)fts5yyLookaheadToken;
fts5yymsp = fts5yypParser->fts5yytos;
-#ifndef NDEBUG
- if( fts5yyTraceFILE && fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) ){
- fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno];
- if( fts5yysize ){
- fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n",
- fts5yyTracePrompt,
- fts5yyruleno, fts5yyRuleName[fts5yyruleno],
- fts5yyruleno<fts5YYNRULE_WITH_ACTION ? "" : " without external action",
- fts5yymsp[fts5yysize].stateno);
- }else{
- fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s.\n",
- fts5yyTracePrompt, fts5yyruleno, fts5yyRuleName[fts5yyruleno],
- fts5yyruleno<fts5YYNRULE_WITH_ACTION ? "" : " without external action");
- }
- }
-#endif /* NDEBUG */
-
- /* Check that the stack is large enough to grow by a single entry
- ** if the RHS of the rule is empty. This ensures that there is room
- ** enough on the stack to push the LHS value */
- if( fts5yyRuleInfoNRhs[fts5yyruleno]==0 ){
-#ifdef fts5YYTRACKMAXSTACKDEPTH
- if( (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)>fts5yypParser->fts5yyhwm ){
- fts5yypParser->fts5yyhwm++;
- assert( fts5yypParser->fts5yyhwm == (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack));
- }
-#endif
-#if fts5YYSTACKDEPTH>0
- if( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystackEnd ){
- fts5yyStackOverflow(fts5yypParser);
- /* The call to fts5yyStackOverflow() above pops the stack until it is
- ** empty, causing the main parser loop to exit. So the return value
- ** is never used and does not matter. */
- return 0;
- }
-#else
- if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5yypParser->fts5yystksz-1] ){
- if( fts5yyGrowStack(fts5yypParser) ){
- fts5yyStackOverflow(fts5yypParser);
- /* The call to fts5yyStackOverflow() above pops the stack until it is
- ** empty, causing the main parser loop to exit. So the return value
- ** is never used and does not matter. */
- return 0;
- }
- fts5yymsp = fts5yypParser->fts5yytos;
- }
-#endif
- }
switch( fts5yyruleno ){
/* Beginning here are the reduction cases. A typical example
@@ -210666,12 +215152,56 @@ static void sqlite3Fts5Parser(
}
#endif
- do{
+ while(1){ /* Exit by "break" */
+ assert( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystack );
assert( fts5yyact==fts5yypParser->fts5yytos->stateno );
fts5yyact = fts5yy_find_shift_action((fts5YYCODETYPE)fts5yymajor,fts5yyact);
if( fts5yyact >= fts5YY_MIN_REDUCE ){
- fts5yyact = fts5yy_reduce(fts5yypParser,fts5yyact-fts5YY_MIN_REDUCE,fts5yymajor,
- fts5yyminor sqlite3Fts5ParserCTX_PARAM);
+ unsigned int fts5yyruleno = fts5yyact - fts5YY_MIN_REDUCE; /* Reduce by this rule */
+ assert( fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) );
+#ifndef NDEBUG
+ if( fts5yyTraceFILE ){
+ int fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno];
+ if( fts5yysize ){
+ fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n",
+ fts5yyTracePrompt,
+ fts5yyruleno, fts5yyRuleName[fts5yyruleno],
+ fts5yyruleno<fts5YYNRULE_WITH_ACTION ? "" : " without external action",
+ fts5yypParser->fts5yytos[fts5yysize].stateno);
+ }else{
+ fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s.\n",
+ fts5yyTracePrompt, fts5yyruleno, fts5yyRuleName[fts5yyruleno],
+ fts5yyruleno<fts5YYNRULE_WITH_ACTION ? "" : " without external action");
+ }
+ }
+#endif /* NDEBUG */
+
+ /* Check that the stack is large enough to grow by a single entry
+ ** if the RHS of the rule is empty. This ensures that there is room
+ ** enough on the stack to push the LHS value */
+ if( fts5yyRuleInfoNRhs[fts5yyruleno]==0 ){
+#ifdef fts5YYTRACKMAXSTACKDEPTH
+ if( (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)>fts5yypParser->fts5yyhwm ){
+ fts5yypParser->fts5yyhwm++;
+ assert( fts5yypParser->fts5yyhwm ==
+ (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack));
+ }
+#endif
+#if fts5YYSTACKDEPTH>0
+ if( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystackEnd ){
+ fts5yyStackOverflow(fts5yypParser);
+ break;
+ }
+#else
+ if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5yypParser->fts5yystksz-1] ){
+ if( fts5yyGrowStack(fts5yypParser) ){
+ fts5yyStackOverflow(fts5yypParser);
+ break;
+ }
+ }
+#endif
+ }
+ fts5yyact = fts5yy_reduce(fts5yypParser,fts5yyruleno,fts5yymajor,fts5yyminor sqlite3Fts5ParserCTX_PARAM);
}else if( fts5yyact <= fts5YY_MAX_SHIFTREDUCE ){
fts5yy_shift(fts5yypParser,fts5yyact,(fts5YYCODETYPE)fts5yymajor,fts5yyminor);
#ifndef fts5YYNOERRORRECOVERY
@@ -210784,7 +215314,7 @@ static void sqlite3Fts5Parser(
break;
#endif
}
- }while( fts5yypParser->fts5yytos>fts5yypParser->fts5yystack );
+ }
#ifndef NDEBUG
if( fts5yyTraceFILE ){
fts5yyStackEntry *i;
@@ -211382,7 +215912,7 @@ static int fts5Bm25GetData(
int rc = SQLITE_OK; /* Return code */
Fts5Bm25Data *p; /* Object to return */
- p = pApi->xGetAuxdata(pFts, 0);
+ p = (Fts5Bm25Data*)pApi->xGetAuxdata(pFts, 0);
if( p==0 ){
int nPhrase; /* Number of phrases in query */
sqlite3_int64 nRow = 0; /* Number of rows in table */
@@ -211456,7 +215986,7 @@ static void fts5Bm25Function(
){
const double k1 = 1.2; /* Constant "k1" from BM25 formula */
const double b = 0.75; /* Constant "b" from BM25 formula */
- int rc = SQLITE_OK; /* Error code */
+ int rc; /* Error code */
double score = 0.0; /* SQL function return value */
Fts5Bm25Data *pData; /* Values allocated/calculated once only */
int i; /* Iterator variable */
@@ -211488,17 +216018,15 @@ static void fts5Bm25Function(
D = (double)nTok;
}
- /* Determine the BM25 score for the current row. */
- for(i=0; rc==SQLITE_OK && i<pData->nPhrase; i++){
- score += pData->aIDF[i] * (
- ( aFreq[i] * (k1 + 1.0) ) /
- ( aFreq[i] + k1 * (1 - b + b * D / pData->avgdl) )
- );
- }
-
- /* If no error has occurred, return the calculated score. Otherwise,
- ** throw an SQL exception. */
+ /* Determine and return the BM25 score for the current row. Or, if an
+ ** error has occurred, throw an exception. */
if( rc==SQLITE_OK ){
+ for(i=0; i<pData->nPhrase; i++){
+ score += pData->aIDF[i] * (
+ ( aFreq[i] * (k1 + 1.0) ) /
+ ( aFreq[i] + k1 * (1 - b + b * D / pData->avgdl) )
+ );
+ }
sqlite3_result_double(pCtx, -1.0 * score);
}else{
sqlite3_result_error_code(pCtx, rc);
@@ -211711,6 +216239,7 @@ static int sqlite3Fts5PoslistNext64(
i64 iOff = *piOff;
int iVal;
fts5FastGetVarint32(a, i, iVal);
+ assert( iVal>=0 );
if( iVal<=1 ){
if( iVal==0 ){
*pi = i;
@@ -211724,9 +216253,12 @@ static int sqlite3Fts5PoslistNext64(
*piOff = -1;
return 1;
}
+ *piOff = iOff + ((iVal-2) & 0x7FFFFFFF);
+ }else{
+ *piOff = (iOff & (i64)0x7FFFFFFF<<32)+((iOff + (iVal-2)) & 0x7FFFFFFF);
}
- *piOff = iOff + ((iVal-2) & 0x7FFFFFFF);
*pi = i;
+ assert( *piOff>=iOff );
return 0;
}
}
@@ -211765,14 +216297,16 @@ static void sqlite3Fts5PoslistSafeAppend(
i64 *piPrev,
i64 iPos
){
- static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32;
- if( (iPos & colmask) != (*piPrev & colmask) ){
- pBuf->p[pBuf->n++] = 1;
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32));
- *piPrev = (iPos & colmask);
+ if( iPos>=*piPrev ){
+ static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32;
+ if( (iPos & colmask) != (*piPrev & colmask) ){
+ pBuf->p[pBuf->n++] = 1;
+ pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32));
+ *piPrev = (iPos & colmask);
+ }
+ pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2);
+ *piPrev = iPos;
}
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2);
- *piPrev = iPos;
}
static int sqlite3Fts5PoslistWriterAppend(
@@ -212262,7 +216796,7 @@ static int fts5ConfigParseSpecial(
rc = SQLITE_ERROR;
}else{
rc = sqlite3Fts5GetTokenizer(pGlobal,
- (const char**)azArg, (int)nArg, &pConfig->pTok, &pConfig->pTokApi,
+ (const char**)azArg, (int)nArg, pConfig,
pzErr
);
}
@@ -212334,9 +216868,7 @@ static int fts5ConfigParseSpecial(
*/
static int fts5ConfigDefaultTokenizer(Fts5Global *pGlobal, Fts5Config *pConfig){
assert( pConfig->pTok==0 && pConfig->pTokApi==0 );
- return sqlite3Fts5GetTokenizer(
- pGlobal, 0, 0, &pConfig->pTok, &pConfig->pTokApi, 0
- );
+ return sqlite3Fts5GetTokenizer(pGlobal, 0, 0, pConfig, 0);
}
/*
@@ -212476,7 +217008,7 @@ static int sqlite3Fts5ConfigParse(
nByte = nArg * (sizeof(char*) + sizeof(u8));
pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, nByte);
- pRet->abUnindexed = (u8*)&pRet->azCol[nArg];
+ pRet->abUnindexed = pRet->azCol ? (u8*)&pRet->azCol[nArg] : 0;
pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1], -1);
pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2], -1);
pRet->bColumnsize = 1;
@@ -213028,6 +217560,7 @@ struct Fts5Parse {
int nPhrase; /* Size of apPhrase array */
Fts5ExprPhrase **apPhrase; /* Array of all phrases */
Fts5ExprNode *pExpr; /* Result of a successful parse */
+ int bPhraseToAnd; /* Convert "a+b" to "a AND b" */
};
static void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){
@@ -213116,6 +217649,7 @@ static void fts5ParseFree(void *p){ sqlite3_free(p); }
static int sqlite3Fts5ExprNew(
Fts5Config *pConfig, /* FTS5 Configuration */
+ int bPhraseToAnd,
int iCol,
const char *zExpr, /* Expression text */
Fts5Expr **ppNew,
@@ -213131,6 +217665,7 @@ static int sqlite3Fts5ExprNew(
*ppNew = 0;
*pzErr = 0;
memset(&sParse, 0, sizeof(sParse));
+ sParse.bPhraseToAnd = bPhraseToAnd;
pEngine = sqlite3Fts5ParserAlloc(fts5ParseAlloc);
if( pEngine==0 ){ return SQLITE_NOMEM; }
sParse.pConfig = pConfig;
@@ -213173,6 +217708,7 @@ static int sqlite3Fts5ExprNew(
pNew->pConfig = pConfig;
pNew->apExprPhrase = sParse.apPhrase;
pNew->nPhrase = sParse.nPhrase;
+ pNew->bDesc = 0;
sParse.apPhrase = 0;
}
}else{
@@ -213185,6 +217721,81 @@ static int sqlite3Fts5ExprNew(
}
/*
+** This function is only called when using the special 'trigram' tokenizer.
+** Argument zText contains the text of a LIKE or GLOB pattern matched
+** against column iCol. This function creates and compiles an FTS5 MATCH
+** expression that will match a superset of the rows matched by the LIKE or
+** GLOB. If successful, SQLITE_OK is returned. Otherwise, an SQLite error
+** code.
+*/
+static int sqlite3Fts5ExprPattern(
+ Fts5Config *pConfig, int bGlob, int iCol, const char *zText, Fts5Expr **pp
+){
+ i64 nText = strlen(zText);
+ char *zExpr = (char*)sqlite3_malloc64(nText*4 + 1);
+ int rc = SQLITE_OK;
+
+ if( zExpr==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ char aSpec[3];
+ int iOut = 0;
+ int i = 0;
+ int iFirst = 0;
+
+ if( bGlob==0 ){
+ aSpec[0] = '_';
+ aSpec[1] = '%';
+ aSpec[2] = 0;
+ }else{
+ aSpec[0] = '*';
+ aSpec[1] = '?';
+ aSpec[2] = '[';
+ }
+
+ while( i<=nText ){
+ if( i==nText
+ || zText[i]==aSpec[0] || zText[i]==aSpec[1] || zText[i]==aSpec[2]
+ ){
+ if( i-iFirst>=3 ){
+ int jj;
+ zExpr[iOut++] = '"';
+ for(jj=iFirst; jj<i; jj++){
+ zExpr[iOut++] = zText[jj];
+ if( zText[jj]=='"' ) zExpr[iOut++] = '"';
+ }
+ zExpr[iOut++] = '"';
+ zExpr[iOut++] = ' ';
+ }
+ if( zText[i]==aSpec[2] ){
+ i += 2;
+ if( zText[i-1]=='^' ) i++;
+ while( i<nText && zText[i]!=']' ) i++;
+ }
+ iFirst = i+1;
+ }
+ i++;
+ }
+ if( iOut>0 ){
+ int bAnd = 0;
+ if( pConfig->eDetail!=FTS5_DETAIL_FULL ){
+ bAnd = 1;
+ if( pConfig->eDetail==FTS5_DETAIL_NONE ){
+ iCol = pConfig->nCol;
+ }
+ }
+ zExpr[iOut] = '\0';
+ rc = sqlite3Fts5ExprNew(pConfig, bAnd, iCol, zExpr, pp,pConfig->pzErrmsg);
+ }else{
+ *pp = 0;
+ }
+ sqlite3_free(zExpr);
+ }
+
+ return rc;
+}
+
+/*
** Free the expression node object passed as the only argument.
*/
static void sqlite3Fts5ParseNodeFree(Fts5ExprNode *p){
@@ -214321,8 +218932,8 @@ static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bD
}
/* If the iterator is not at a real match, skip forward until it is. */
- while( pRoot->bNomatch ){
- assert( pRoot->bEof==0 && rc==SQLITE_OK );
+ while( pRoot->bNomatch && rc==SQLITE_OK ){
+ assert( pRoot->bEof==0 );
rc = fts5ExprNodeNext(p, pRoot, 0, 0);
}
return rc;
@@ -214561,6 +219172,20 @@ static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p){
pParse->pExpr = p;
}
+static int parseGrowPhraseArray(Fts5Parse *pParse){
+ if( (pParse->nPhrase % 8)==0 ){
+ sqlite3_int64 nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8);
+ Fts5ExprPhrase **apNew;
+ apNew = (Fts5ExprPhrase**)sqlite3_realloc64(pParse->apPhrase, nByte);
+ if( apNew==0 ){
+ pParse->rc = SQLITE_NOMEM;
+ return SQLITE_NOMEM;
+ }
+ pParse->apPhrase = apNew;
+ }
+ return SQLITE_OK;
+}
+
/*
** This function is called by the parser to process a string token. The
** string may or may not be quoted. In any case it is tokenized and a
@@ -214596,16 +219221,9 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm(
}else{
if( pAppend==0 ){
- if( (pParse->nPhrase % 8)==0 ){
- sqlite3_int64 nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8);
- Fts5ExprPhrase **apNew;
- apNew = (Fts5ExprPhrase**)sqlite3_realloc64(pParse->apPhrase, nByte);
- if( apNew==0 ){
- pParse->rc = SQLITE_NOMEM;
- fts5ExprPhraseFree(sCtx.pPhrase);
- return 0;
- }
- pParse->apPhrase = apNew;
+ if( parseGrowPhraseArray(pParse) ){
+ fts5ExprPhraseFree(sCtx.pPhrase);
+ return 0;
}
pParse->nPhrase++;
}
@@ -215013,6 +219631,67 @@ static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){
}
/*
+** This function is used when parsing LIKE or GLOB patterns against
+** trigram indexes that specify either detail=column or detail=none.
+** It converts a phrase:
+**
+** abc + def + ghi
+**
+** into an AND tree:
+**
+** abc AND def AND ghi
+*/
+static Fts5ExprNode *fts5ParsePhraseToAnd(
+ Fts5Parse *pParse,
+ Fts5ExprNearset *pNear
+){
+ int nTerm = pNear->apPhrase[0]->nTerm;
+ int ii;
+ int nByte;
+ Fts5ExprNode *pRet;
+
+ assert( pNear->nPhrase==1 );
+ assert( pParse->bPhraseToAnd );
+
+ nByte = sizeof(Fts5ExprNode) + nTerm*sizeof(Fts5ExprNode*);
+ pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte);
+ if( pRet ){
+ pRet->eType = FTS5_AND;
+ pRet->nChild = nTerm;
+ fts5ExprAssignXNext(pRet);
+ pParse->nPhrase--;
+ for(ii=0; ii<nTerm; ii++){
+ Fts5ExprPhrase *pPhrase = (Fts5ExprPhrase*)sqlite3Fts5MallocZero(
+ &pParse->rc, sizeof(Fts5ExprPhrase)
+ );
+ if( pPhrase ){
+ if( parseGrowPhraseArray(pParse) ){
+ fts5ExprPhraseFree(pPhrase);
+ }else{
+ pParse->apPhrase[pParse->nPhrase++] = pPhrase;
+ pPhrase->nTerm = 1;
+ pPhrase->aTerm[0].zTerm = sqlite3Fts5Strndup(
+ &pParse->rc, pNear->apPhrase[0]->aTerm[ii].zTerm, -1
+ );
+ pRet->apChild[ii] = sqlite3Fts5ParseNode(pParse, FTS5_STRING,
+ 0, 0, sqlite3Fts5ParseNearset(pParse, 0, pPhrase)
+ );
+ }
+ }
+ }
+
+ if( pParse->rc ){
+ sqlite3Fts5ParseNodeFree(pRet);
+ pRet = 0;
+ }else{
+ sqlite3Fts5ParseNearsetFree(pNear);
+ }
+ }
+
+ return pRet;
+}
+
+/*
** Allocate and return a new expression object. If anything goes wrong (i.e.
** OOM error), leave an error code in pParse and return NULL.
*/
@@ -215036,51 +219715,58 @@ static Fts5ExprNode *sqlite3Fts5ParseNode(
if( eType!=FTS5_STRING && pLeft==0 ) return pRight;
if( eType!=FTS5_STRING && pRight==0 ) return pLeft;
- if( eType==FTS5_NOT ){
- nChild = 2;
- }else if( eType==FTS5_AND || eType==FTS5_OR ){
- nChild = 2;
- if( pLeft->eType==eType ) nChild += pLeft->nChild-1;
- if( pRight->eType==eType ) nChild += pRight->nChild-1;
- }
+ if( eType==FTS5_STRING
+ && pParse->bPhraseToAnd
+ && pNear->apPhrase[0]->nTerm>1
+ ){
+ pRet = fts5ParsePhraseToAnd(pParse, pNear);
+ }else{
+ if( eType==FTS5_NOT ){
+ nChild = 2;
+ }else if( eType==FTS5_AND || eType==FTS5_OR ){
+ nChild = 2;
+ if( pLeft->eType==eType ) nChild += pLeft->nChild-1;
+ if( pRight->eType==eType ) nChild += pRight->nChild-1;
+ }
- nByte = sizeof(Fts5ExprNode) + sizeof(Fts5ExprNode*)*(nChild-1);
- pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte);
+ nByte = sizeof(Fts5ExprNode) + sizeof(Fts5ExprNode*)*(nChild-1);
+ pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte);
- if( pRet ){
- pRet->eType = eType;
- pRet->pNear = pNear;
- fts5ExprAssignXNext(pRet);
- if( eType==FTS5_STRING ){
- int iPhrase;
- for(iPhrase=0; iPhrase<pNear->nPhrase; iPhrase++){
- pNear->apPhrase[iPhrase]->pNode = pRet;
- if( pNear->apPhrase[iPhrase]->nTerm==0 ){
- pRet->xNext = 0;
- pRet->eType = FTS5_EOF;
+ if( pRet ){
+ pRet->eType = eType;
+ pRet->pNear = pNear;
+ fts5ExprAssignXNext(pRet);
+ if( eType==FTS5_STRING ){
+ int iPhrase;
+ for(iPhrase=0; iPhrase<pNear->nPhrase; iPhrase++){
+ pNear->apPhrase[iPhrase]->pNode = pRet;
+ if( pNear->apPhrase[iPhrase]->nTerm==0 ){
+ pRet->xNext = 0;
+ pRet->eType = FTS5_EOF;
+ }
}
- }
- if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL ){
- Fts5ExprPhrase *pPhrase = pNear->apPhrase[0];
- if( pNear->nPhrase!=1
- || pPhrase->nTerm>1
- || (pPhrase->nTerm>0 && pPhrase->aTerm[0].bFirst)
- ){
- assert( pParse->rc==SQLITE_OK );
- pParse->rc = SQLITE_ERROR;
- assert( pParse->zErr==0 );
- pParse->zErr = sqlite3_mprintf(
- "fts5: %s queries are not supported (detail!=full)",
- pNear->nPhrase==1 ? "phrase": "NEAR"
- );
- sqlite3_free(pRet);
- pRet = 0;
+ if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL ){
+ Fts5ExprPhrase *pPhrase = pNear->apPhrase[0];
+ if( pNear->nPhrase!=1
+ || pPhrase->nTerm>1
+ || (pPhrase->nTerm>0 && pPhrase->aTerm[0].bFirst)
+ ){
+ assert( pParse->rc==SQLITE_OK );
+ pParse->rc = SQLITE_ERROR;
+ assert( pParse->zErr==0 );
+ pParse->zErr = sqlite3_mprintf(
+ "fts5: %s queries are not supported (detail!=full)",
+ pNear->nPhrase==1 ? "phrase": "NEAR"
+ );
+ sqlite3_free(pRet);
+ pRet = 0;
+ }
}
+ }else{
+ fts5ExprAddChildren(pRet, pLeft);
+ fts5ExprAddChildren(pRet, pRight);
}
- }else{
- fts5ExprAddChildren(pRet, pLeft);
- fts5ExprAddChildren(pRet, pRight);
}
}
}
@@ -215158,6 +219844,7 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
return pRet;
}
+#ifdef SQLITE_TEST
static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
sqlite3_int64 nByte = 0;
Fts5ExprTerm *p;
@@ -215301,8 +219988,17 @@ static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
int iTerm;
if( pNear->pColset ){
- int iCol = pNear->pColset->aiCol[0];
- zRet = fts5PrintfAppend(zRet, "%s : ", pConfig->azCol[iCol]);
+ int ii;
+ Fts5Colset *pColset = pNear->pColset;
+ if( pColset->nCol>1 ) zRet = fts5PrintfAppend(zRet, "{");
+ for(ii=0; ii<pColset->nCol; ii++){
+ zRet = fts5PrintfAppend(zRet, "%s%s",
+ pConfig->azCol[pColset->aiCol[ii]], ii==pColset->nCol-1 ? "" : " "
+ );
+ }
+ if( zRet ){
+ zRet = fts5PrintfAppend(zRet, "%s : ", pColset->nCol>1 ? "}" : "");
+ }
if( zRet==0 ) return 0;
}
@@ -215425,7 +220121,7 @@ static void fts5ExprFunction(
rc = sqlite3Fts5ConfigParse(pGlobal, db, nConfig, azConfig, &pConfig, &zErr);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5ExprNew(pConfig, pConfig->nCol, zExpr, &pExpr, &zErr);
+ rc = sqlite3Fts5ExprNew(pConfig, 0, pConfig->nCol, zExpr, &pExpr, &zErr);
}
if( rc==SQLITE_OK ){
char *zText;
@@ -215515,12 +220211,14 @@ static void fts5ExprFold(
sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics));
}
}
+#endif /* ifdef SQLITE_TEST */
/*
** This is called during initialization to register the fts5_expr() scalar
** UDF with the SQLite handle passed as the only argument.
*/
static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
+#ifdef SQLITE_TEST
struct Fts5ExprFunc {
const char *z;
void (*x)(sqlite3_context*,int,sqlite3_value**);
@@ -215538,6 +220236,10 @@ static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
struct Fts5ExprFunc *p = &aFunc[i];
rc = sqlite3_create_function(db, p->z, -1, SQLITE_UTF8, pCtx, p->x, 0, 0);
}
+#else
+ int rc = SQLITE_OK;
+ UNUSED_PARAM2(pGlobal,db);
+#endif
/* Avoid warnings indicating that sqlite3Fts5ParserTrace() and
** sqlite3Fts5ParserFallback() are unused */
@@ -216098,7 +220800,6 @@ static int sqlite3Fts5HashWrite(
p->iCol = (pHash->eDetail==FTS5_DETAIL_FULL ? 0 : -1);
}
- nIncr += p->nData;
}else{
/* Appending to an existing hash-entry. Check that there is enough
@@ -216131,8 +220832,9 @@ static int sqlite3Fts5HashWrite(
/* If this is a new rowid, append the 4-byte size field for the previous
** entry, and the new rowid for this entry. */
if( iRowid!=p->iRowid ){
+ u64 iDiff = (u64)iRowid - (u64)p->iRowid;
fts5HashAddPoslistSize(pHash, p, 0);
- p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iRowid - p->iRowid);
+ p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iDiff);
p->iRowid = iRowid;
bNew = 1;
p->iSzPoslist = p->nData;
@@ -216784,7 +221486,7 @@ struct Fts5SegIter {
int iLeafPgno; /* Current leaf page number */
Fts5Data *pLeaf; /* Current leaf data */
Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */
- int iLeafOffset; /* Byte offset within current leaf */
+ i64 iLeafOffset; /* Byte offset within current leaf */
/* Next method */
void (*xNext)(Fts5Index*, Fts5SegIter*, int*);
@@ -217964,7 +222666,7 @@ static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){
static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){
u8 *a = pIter->pLeaf->p; /* Buffer to read data from */
- int iOff = pIter->iLeafOffset;
+ i64 iOff = pIter->iLeafOffset;
ASSERT_SZLEAF_OK(pIter->pLeaf);
if( iOff>=pIter->pLeaf->szLeaf ){
@@ -217997,7 +222699,7 @@ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){
*/
static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){
u8 *a = pIter->pLeaf->p; /* Buffer to read data from */
- int iOff = pIter->iLeafOffset; /* Offset to read at */
+ i64 iOff = pIter->iLeafOffset; /* Offset to read at */
int nNew; /* Bytes of new data */
iOff += fts5GetVarint32(&a[iOff], nNew);
@@ -218107,7 +222809,7 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){
ASSERT_SZLEAF_OK(pIter->pLeaf);
while( 1 ){
- i64 iDelta = 0;
+ u64 iDelta = 0;
if( eDetail==FTS5_DETAIL_NONE ){
/* todo */
@@ -218122,7 +222824,7 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){
i += nPos;
}
if( i>=n ) break;
- i += fts5GetVarint(&a[i], (u64*)&iDelta);
+ i += fts5GetVarint(&a[i], &iDelta);
pIter->iRowid += iDelta;
/* If necessary, grow the pIter->aRowidOffset[] array. */
@@ -218221,7 +222923,7 @@ static void fts5SegIterNext_Reverse(
if( pIter->iRowidOffset>0 ){
u8 *a = pIter->pLeaf->p;
int iOff;
- i64 iDelta;
+ u64 iDelta;
pIter->iRowidOffset--;
pIter->iLeafOffset = pIter->aRowidOffset[pIter->iRowidOffset];
@@ -218230,7 +222932,7 @@ static void fts5SegIterNext_Reverse(
if( p->pConfig->eDetail!=FTS5_DETAIL_NONE ){
iOff += pIter->nPos;
}
- fts5GetVarint(&a[iOff], (u64*)&iDelta);
+ fts5GetVarint(&a[iOff], &iDelta);
pIter->iRowid -= iDelta;
}else{
fts5SegIterReverseNewPage(p, pIter);
@@ -218423,14 +223125,9 @@ static void fts5SegIterNext(
}else{
/* The following could be done by calling fts5SegIterLoadNPos(). But
** this block is particularly performance critical, so equivalent
- ** code is inlined.
- **
- ** Later: Switched back to fts5SegIterLoadNPos() because it supports
- ** detail=none mode. Not ideal.
- */
+ ** code is inlined. */
int nSz;
- assert( p->rc==SQLITE_OK );
- assert( pIter->iLeafOffset<=pIter->pLeaf->nn );
+ assert_nc( pIter->iLeafOffset<=pIter->pLeaf->nn );
fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz);
pIter->bDel = (nSz & 0x0001);
pIter->nPos = nSz>>1;
@@ -219422,7 +224119,7 @@ static void fts5ChunkIterate(
int pgno = pSeg->iLeafPgno;
int pgnoSave = 0;
- /* This function does notmwork with detail=none databases. */
+ /* This function does not work with detail=none databases. */
assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE );
if( (pSeg->flags & FTS5_SEGITER_REVERSE)==0 ){
@@ -219435,6 +224132,9 @@ static void fts5ChunkIterate(
fts5DataRelease(pData);
if( nRem<=0 ){
break;
+ }else if( pSeg->pSeg==0 ){
+ p->rc = FTS5_CORRUPT;
+ return;
}else{
pgno++;
pData = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno));
@@ -219486,66 +224186,72 @@ static void fts5SegiterPoslist(
}
/*
-** IN/OUT parameter (*pa) points to a position list n bytes in size. If
-** the position list contains entries for column iCol, then (*pa) is set
-** to point to the sub-position-list for that column and the number of
-** bytes in it returned. Or, if the argument position list does not
-** contain any entries for column iCol, return 0.
+** Parameter pPos points to a buffer containing a position list, size nPos.
+** This function filters it according to pColset (which must be non-NULL)
+** and sets pIter->base.pData/nData to point to the new position list.
+** If memory is required for the new position list, use buffer pIter->poslist.
+** Or, if the new position list is a contiguous subset of the input, set
+** pIter->base.pData/nData to point directly to it.
+**
+** This function is a no-op if *pRc is other than SQLITE_OK when it is
+** called. If an OOM error is encountered, *pRc is set to SQLITE_NOMEM
+** before returning.
*/
-static int fts5IndexExtractCol(
- const u8 **pa, /* IN/OUT: Pointer to poslist */
- int n, /* IN: Size of poslist in bytes */
- int iCol /* Column to extract from poslist */
-){
- int iCurrent = 0; /* Anything before the first 0x01 is col 0 */
- const u8 *p = *pa;
- const u8 *pEnd = &p[n]; /* One byte past end of position list */
-
- while( iCol>iCurrent ){
- /* Advance pointer p until it points to pEnd or an 0x01 byte that is
- ** not part of a varint. Note that it is not possible for a negative
- ** or extremely large varint to occur within an uncorrupted position
- ** list. So the last byte of each varint may be assumed to have a clear
- ** 0x80 bit. */
- while( *p!=0x01 ){
- while( *p++ & 0x80 );
- if( p>=pEnd ) return 0;
- }
- *pa = p++;
- iCurrent = *p++;
- if( iCurrent & 0x80 ){
- p--;
- p += fts5GetVarint32(p, iCurrent);
- }
- }
- if( iCol!=iCurrent ) return 0;
-
- /* Advance pointer p until it points to pEnd or an 0x01 byte that is
- ** not part of a varint */
- while( p<pEnd && *p!=0x01 ){
- while( *p++ & 0x80 );
- }
-
- return p - (*pa);
-}
-
static void fts5IndexExtractColset(
int *pRc,
Fts5Colset *pColset, /* Colset to filter on */
const u8 *pPos, int nPos, /* Position list */
- Fts5Buffer *pBuf /* Output buffer */
+ Fts5Iter *pIter
){
if( *pRc==SQLITE_OK ){
- int i;
- fts5BufferZero(pBuf);
- for(i=0; i<pColset->nCol; i++){
- const u8 *pSub = pPos;
- int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]);
- if( nSub ){
- fts5BufferAppendBlob(pRc, pBuf, nSub, pSub);
+ const u8 *p = pPos;
+ const u8 *aCopy = p;
+ const u8 *pEnd = &p[nPos]; /* One byte past end of position list */
+ int i = 0;
+ int iCurrent = 0;
+
+ if( pColset->nCol>1 && sqlite3Fts5BufferSize(pRc, &pIter->poslist, nPos) ){
+ return;
+ }
+
+ while( 1 ){
+ while( pColset->aiCol[i]<iCurrent ){
+ i++;
+ if( i==pColset->nCol ){
+ pIter->base.pData = pIter->poslist.p;
+ pIter->base.nData = pIter->poslist.n;
+ return;
+ }
+ }
+
+ /* Advance pointer p until it points to pEnd or an 0x01 byte that is
+ ** not part of a varint */
+ while( p<pEnd && *p!=0x01 ){
+ while( *p++ & 0x80 );
+ }
+
+ if( pColset->aiCol[i]==iCurrent ){
+ if( pColset->nCol==1 ){
+ pIter->base.pData = aCopy;
+ pIter->base.nData = p-aCopy;
+ return;
+ }
+ fts5BufferSafeAppendBlob(&pIter->poslist, aCopy, p-aCopy);
+ }
+ if( p>=pEnd ){
+ pIter->base.pData = pIter->poslist.p;
+ pIter->base.nData = pIter->poslist.n;
+ return;
+ }
+ aCopy = p++;
+ iCurrent = *p++;
+ if( iCurrent & 0x80 ){
+ p--;
+ p += fts5GetVarint32(p, iCurrent);
}
}
}
+
}
/*
@@ -219665,16 +224371,9 @@ static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){
/* All data is stored on the current page. Populate the output
** variables to point into the body of the page object. */
const u8 *a = &pSeg->pLeaf->p[pSeg->iLeafOffset];
- if( pColset->nCol==1 ){
- pIter->base.nData = fts5IndexExtractCol(&a, pSeg->nPos,pColset->aiCol[0]);
- pIter->base.pData = a;
- }else{
- int *pRc = &pIter->pIndex->rc;
- fts5BufferZero(&pIter->poslist);
- fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, &pIter->poslist);
- pIter->base.pData = pIter->poslist.p;
- pIter->base.nData = pIter->poslist.n;
- }
+ int *pRc = &pIter->pIndex->rc;
+ fts5BufferZero(&pIter->poslist);
+ fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, pIter);
}else{
/* The data is distributed over two or more pages. Copy it into the
** Fts5Iter.poslist buffer and then set the output pointer to point
@@ -220896,14 +225595,14 @@ static void fts5FlushOneHash(Fts5Index *p){
fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist);
}else{
i64 iRowid = 0;
- i64 iDelta = 0;
+ u64 iDelta = 0;
int iOff = 0;
/* The entire doclist will not fit on this leaf. The following
** loop iterates through the poslists that make up the current
** doclist. */
while( p->rc==SQLITE_OK && iOff<nDoclist ){
- iOff += fts5GetVarint(&pDoclist[iOff], (u64*)&iDelta);
+ iOff += fts5GetVarint(&pDoclist[iOff], &iDelta);
iRowid += iDelta;
if( writer.bFirstRowidInPage ){
@@ -221157,7 +225856,7 @@ static void fts5AppendPoslist(
static void fts5DoclistIterNext(Fts5DoclistIter *pIter){
u8 *p = pIter->aPoslist + pIter->nSize + pIter->nPoslist;
- assert( pIter->aPoslist );
+ assert( pIter->aPoslist || (p==0 && pIter->aPoslist==0) );
if( p>=pIter->aEof ){
pIter->aPoslist = 0;
}else{
@@ -221177,6 +225876,9 @@ static void fts5DoclistIterNext(Fts5DoclistIter *pIter){
}
pIter->aPoslist = p;
+ if( &pIter->aPoslist[pIter->nPoslist]>pIter->aEof ){
+ pIter->aPoslist = 0;
+ }
}
}
@@ -221185,9 +225887,11 @@ static void fts5DoclistIterInit(
Fts5DoclistIter *pIter
){
memset(pIter, 0, sizeof(*pIter));
- pIter->aPoslist = pBuf->p;
- pIter->aEof = &pBuf->p[pBuf->n];
- fts5DoclistIterNext(pIter);
+ if( pBuf->n>0 ){
+ pIter->aPoslist = pBuf->p;
+ pIter->aEof = &pBuf->p[pBuf->n];
+ fts5DoclistIterNext(pIter);
+ }
}
#if 0
@@ -221241,16 +225945,20 @@ static void fts5NextRowid(Fts5Buffer *pBuf, int *piOff, i64 *piRowid){
static void fts5MergeRowidLists(
Fts5Index *p, /* FTS5 backend object */
Fts5Buffer *p1, /* First list to merge */
- Fts5Buffer *p2 /* Second list to merge */
+ int nBuf, /* Number of entries in apBuf[] */
+ Fts5Buffer *aBuf /* Array of other lists to merge into p1 */
){
int i1 = 0;
int i2 = 0;
i64 iRowid1 = 0;
i64 iRowid2 = 0;
i64 iOut = 0;
-
+ Fts5Buffer *p2 = &aBuf[0];
Fts5Buffer out;
+
+ (void)nBuf;
memset(&out, 0, sizeof(out));
+ assert( nBuf==1 );
sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n);
if( p->rc ) return;
@@ -221277,177 +225985,214 @@ static void fts5MergeRowidLists(
fts5BufferFree(&out);
}
+typedef struct PrefixMerger PrefixMerger;
+struct PrefixMerger {
+ Fts5DoclistIter iter; /* Doclist iterator */
+ i64 iPos; /* For iterating through a position list */
+ int iOff;
+ u8 *aPos;
+ PrefixMerger *pNext; /* Next in docid/poslist order */
+};
+
+static void fts5PrefixMergerInsertByRowid(
+ PrefixMerger **ppHead,
+ PrefixMerger *p
+){
+ if( p->iter.aPoslist ){
+ PrefixMerger **pp = ppHead;
+ while( *pp && p->iter.iRowid>(*pp)->iter.iRowid ){
+ pp = &(*pp)->pNext;
+ }
+ p->pNext = *pp;
+ *pp = p;
+ }
+}
+
+static void fts5PrefixMergerInsertByPosition(
+ PrefixMerger **ppHead,
+ PrefixMerger *p
+){
+ if( p->iPos>=0 ){
+ PrefixMerger **pp = ppHead;
+ while( *pp && p->iPos>(*pp)->iPos ){
+ pp = &(*pp)->pNext;
+ }
+ p->pNext = *pp;
+ *pp = p;
+ }
+}
+
+
/*
-** Buffers p1 and p2 contain doclists. This function merges the content
-** of the two doclists together and sets buffer p1 to the result before
-** returning.
-**
-** If an error occurs, an error code is left in p->rc. If an error has
-** already occurred, this function is a no-op.
+** Array aBuf[] contains nBuf doclists. These are all merged in with the
+** doclist in buffer p1.
*/
static void fts5MergePrefixLists(
Fts5Index *p, /* FTS5 backend object */
Fts5Buffer *p1, /* First list to merge */
- Fts5Buffer *p2 /* Second list to merge */
-){
- if( p2->n ){
- i64 iLastRowid = 0;
- Fts5DoclistIter i1;
- Fts5DoclistIter i2;
- Fts5Buffer out = {0, 0, 0};
- Fts5Buffer tmp = {0, 0, 0};
-
- /* The maximum size of the output is equal to the sum of the two
- ** input sizes + 1 varint (9 bytes). The extra varint is because if the
- ** first rowid in one input is a large negative number, and the first in
- ** the other a non-negative number, the delta for the non-negative
- ** number will be larger on disk than the literal integer value
- ** was.
- **
- ** Or, if the input position-lists are corrupt, then the output might
- ** include up to 2 extra 10-byte positions created by interpreting -1
- ** (the value PoslistNext64() uses for EOF) as a position and appending
- ** it to the output. This can happen at most once for each input
- ** position-list, hence two 10 byte paddings. */
- if( sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n + 9+10+10) ) return;
- fts5DoclistIterInit(p1, &i1);
- fts5DoclistIterInit(p2, &i2);
+ int nBuf, /* Number of buffers in array aBuf[] */
+ Fts5Buffer *aBuf /* Other lists to merge in */
+){
+#define fts5PrefixMergerNextPosition(p) \
+ sqlite3Fts5PoslistNext64((p)->aPos,(p)->iter.nPoslist,&(p)->iOff,&(p)->iPos)
+#define FTS5_MERGE_NLIST 16
+ PrefixMerger aMerger[FTS5_MERGE_NLIST];
+ PrefixMerger *pHead = 0;
+ int i;
+ int nOut = 0;
+ Fts5Buffer out = {0, 0, 0};
+ Fts5Buffer tmp = {0, 0, 0};
+ i64 iLastRowid = 0;
+
+ /* Initialize a doclist-iterator for each input buffer. Arrange them in
+ ** a linked-list starting at pHead in ascending order of rowid. Avoid
+ ** linking any iterators already at EOF into the linked list at all. */
+ assert( nBuf+1<=sizeof(aMerger)/sizeof(aMerger[0]) );
+ memset(aMerger, 0, sizeof(PrefixMerger)*(nBuf+1));
+ pHead = &aMerger[nBuf];
+ fts5DoclistIterInit(p1, &pHead->iter);
+ for(i=0; i<nBuf; i++){
+ fts5DoclistIterInit(&aBuf[i], &aMerger[i].iter);
+ fts5PrefixMergerInsertByRowid(&pHead, &aMerger[i]);
+ nOut += aBuf[i].n;
+ }
+ if( nOut==0 ) return;
+ nOut += p1->n + 9 + 10*nBuf;
+
+ /* The maximum size of the output is equal to the sum of the
+ ** input sizes + 1 varint (9 bytes). The extra varint is because if the
+ ** first rowid in one input is a large negative number, and the first in
+ ** the other a non-negative number, the delta for the non-negative
+ ** number will be larger on disk than the literal integer value
+ ** was.
+ **
+ ** Or, if the input position-lists are corrupt, then the output might
+ ** include up to (nBuf+1) extra 10-byte positions created by interpreting -1
+ ** (the value PoslistNext64() uses for EOF) as a position and appending
+ ** it to the output. This can happen at most once for each input
+ ** position-list, hence (nBuf+1) 10 byte paddings. */
+ if( sqlite3Fts5BufferSize(&p->rc, &out, nOut) ) return;
+
+ while( pHead ){
+ fts5MergeAppendDocid(&out, iLastRowid, pHead->iter.iRowid);
+
+ if( pHead->pNext && iLastRowid==pHead->pNext->iter.iRowid ){
+ /* Merge data from two or more poslists */
+ i64 iPrev = 0;
+ int nTmp = FTS5_DATA_ZERO_PADDING;
+ int nMerge = 0;
+ PrefixMerger *pSave = pHead;
+ PrefixMerger *pThis = 0;
+ int nTail = 0;
+
+ pHead = 0;
+ while( pSave && pSave->iter.iRowid==iLastRowid ){
+ PrefixMerger *pNext = pSave->pNext;
+ pSave->iOff = 0;
+ pSave->iPos = 0;
+ pSave->aPos = &pSave->iter.aPoslist[pSave->iter.nSize];
+ fts5PrefixMergerNextPosition(pSave);
+ nTmp += pSave->iter.nPoslist + 10;
+ nMerge++;
+ fts5PrefixMergerInsertByPosition(&pHead, pSave);
+ pSave = pNext;
+ }
+
+ if( pHead==0 || pHead->pNext==0 ){
+ p->rc = FTS5_CORRUPT;
+ break;
+ }
- while( 1 ){
- if( i1.iRowid<i2.iRowid ){
- /* Copy entry from i1 */
- fts5MergeAppendDocid(&out, iLastRowid, i1.iRowid);
- fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.nPoslist+i1.nSize);
- fts5DoclistIterNext(&i1);
- if( i1.aPoslist==0 ) break;
- assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) );
- }
- else if( i2.iRowid!=i1.iRowid ){
- /* Copy entry from i2 */
- fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid);
- fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.nPoslist+i2.nSize);
- fts5DoclistIterNext(&i2);
- if( i2.aPoslist==0 ) break;
- assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) );
+ /* See the earlier comment in this function for an explanation of why
+ ** corrupt input position lists might cause the output to consume
+ ** at most nMerge*10 bytes of unexpected space. */
+ if( sqlite3Fts5BufferSize(&p->rc, &tmp, nTmp+nMerge*10) ){
+ break;
}
- else{
- /* Merge the two position lists. */
- i64 iPos1 = 0;
- i64 iPos2 = 0;
- int iOff1 = 0;
- int iOff2 = 0;
- u8 *a1 = &i1.aPoslist[i1.nSize];
- u8 *a2 = &i2.aPoslist[i2.nSize];
- int nCopy;
- u8 *aCopy;
-
- i64 iPrev = 0;
- Fts5PoslistWriter writer;
- memset(&writer, 0, sizeof(writer));
-
- /* See the earlier comment in this function for an explanation of why
- ** corrupt input position lists might cause the output to consume
- ** at most 20 bytes of unexpected space. */
- fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid);
- fts5BufferZero(&tmp);
- sqlite3Fts5BufferSize(&p->rc, &tmp, i1.nPoslist + i2.nPoslist + 10 + 10);
- if( p->rc ) break;
-
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
- sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
- assert_nc( iPos1>=0 && iPos2>=0 );
-
- if( iPos1<iPos2 ){
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
- }else{
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
- sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
- }
- if( iPos1>=0 && iPos2>=0 ){
- while( 1 ){
- if( iPos1<iPos2 ){
- if( iPos1!=iPrev ){
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
- }
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
- if( iPos1<0 ) break;
- }else{
- assert_nc( iPos2!=iPrev );
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
- sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
- if( iPos2<0 ) break;
- }
- }
- }
+ fts5BufferZero(&tmp);
- if( iPos1>=0 ){
- if( iPos1!=iPrev ){
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
- }
- aCopy = &a1[iOff1];
- nCopy = i1.nPoslist - iOff1;
- }else{
- assert_nc( iPos2>=0 && iPos2!=iPrev );
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
- aCopy = &a2[iOff2];
- nCopy = i2.nPoslist - iOff2;
- }
- if( nCopy>0 ){
- fts5BufferSafeAppendBlob(&tmp, aCopy, nCopy);
+ pThis = pHead;
+ pHead = pThis->pNext;
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos);
+ fts5PrefixMergerNextPosition(pThis);
+ fts5PrefixMergerInsertByPosition(&pHead, pThis);
+
+ while( pHead->pNext ){
+ pThis = pHead;
+ if( pThis->iPos!=iPrev ){
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos);
}
+ fts5PrefixMergerNextPosition(pThis);
+ pHead = pThis->pNext;
+ fts5PrefixMergerInsertByPosition(&pHead, pThis);
+ }
- /* WRITEPOSLISTSIZE */
- assert_nc( tmp.n<=i1.nPoslist+i2.nPoslist );
- assert( tmp.n<=i1.nPoslist+i2.nPoslist+10+10 );
- if( tmp.n>i1.nPoslist+i2.nPoslist ){
- if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
- break;
+ if( pHead->iPos!=iPrev ){
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pHead->iPos);
+ }
+ nTail = pHead->iter.nPoslist - pHead->iOff;
+
+ /* WRITEPOSLISTSIZE */
+ assert_nc( tmp.n+nTail<=nTmp );
+ assert( tmp.n+nTail<=nTmp+nMerge*10 );
+ if( tmp.n+nTail>nTmp-FTS5_DATA_ZERO_PADDING ){
+ if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
+ break;
+ }
+ fts5BufferSafeAppendVarint(&out, (tmp.n+nTail) * 2);
+ fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n);
+ if( nTail>0 ){
+ fts5BufferSafeAppendBlob(&out, &pHead->aPos[pHead->iOff], nTail);
+ }
+
+ pHead = pSave;
+ for(i=0; i<nBuf+1; i++){
+ PrefixMerger *pX = &aMerger[i];
+ if( pX->iter.aPoslist && pX->iter.iRowid==iLastRowid ){
+ fts5DoclistIterNext(&pX->iter);
+ fts5PrefixMergerInsertByRowid(&pHead, pX);
}
- fts5BufferSafeAppendVarint(&out, tmp.n * 2);
- fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n);
- fts5DoclistIterNext(&i1);
- fts5DoclistIterNext(&i2);
- assert_nc( out.n<=(p1->n+p2->n+9) );
- if( i1.aPoslist==0 || i2.aPoslist==0 ) break;
- assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) );
}
- }
- if( i1.aPoslist ){
- fts5MergeAppendDocid(&out, iLastRowid, i1.iRowid);
- fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.aEof - i1.aPoslist);
- }
- else if( i2.aPoslist ){
- fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid);
- fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.aEof - i2.aPoslist);
+ }else{
+ /* Copy poslist from pHead to output */
+ PrefixMerger *pThis = pHead;
+ Fts5DoclistIter *pI = &pThis->iter;
+ fts5BufferSafeAppendBlob(&out, pI->aPoslist, pI->nPoslist+pI->nSize);
+ fts5DoclistIterNext(pI);
+ pHead = pThis->pNext;
+ fts5PrefixMergerInsertByRowid(&pHead, pThis);
}
- assert_nc( out.n<=(p1->n+p2->n+9) );
-
- fts5BufferSet(&p->rc, p1, out.n, out.p);
- fts5BufferFree(&tmp);
- fts5BufferFree(&out);
}
+
+ fts5BufferFree(p1);
+ fts5BufferFree(&tmp);
+ memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING);
+ *p1 = out;
}
static void fts5SetupPrefixIter(
Fts5Index *p, /* Index to read from */
int bDesc, /* True for "ORDER BY rowid DESC" */
- const u8 *pToken, /* Buffer containing prefix to match */
+ int iIdx, /* Index to scan for data */
+ u8 *pToken, /* Buffer containing prefix to match */
int nToken, /* Size of buffer pToken in bytes */
Fts5Colset *pColset, /* Restrict matches to these columns */
Fts5Iter **ppIter /* OUT: New iterator */
){
Fts5Structure *pStruct;
Fts5Buffer *aBuf;
- const int nBuf = 32;
+ int nBuf = 32;
+ int nMerge = 1;
- void (*xMerge)(Fts5Index*, Fts5Buffer*, Fts5Buffer*);
+ void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*);
void (*xAppend)(Fts5Index*, i64, Fts5Iter*, Fts5Buffer*);
if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){
xMerge = fts5MergeRowidLists;
xAppend = fts5AppendRowid;
}else{
+ nMerge = FTS5_MERGE_NLIST-1;
+ nBuf = nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */
xMerge = fts5MergePrefixLists;
xAppend = fts5AppendPoslist;
}
@@ -221467,6 +226212,27 @@ static void fts5SetupPrefixIter(
int bNewTerm = 1;
memset(&doclist, 0, sizeof(doclist));
+ if( iIdx!=0 ){
+ int dummy = 0;
+ const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT;
+ pToken[0] = FTS5_MAIN_PREFIX;
+ fts5MultiIterNew(p, pStruct, f2, pColset, pToken, nToken, -1, 0, &p1);
+ fts5IterSetOutputCb(&p->rc, p1);
+ for(;
+ fts5MultiIterEof(p, p1)==0;
+ fts5MultiIterNext2(p, p1, &dummy)
+ ){
+ Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ];
+ p1->xSetOutputs(p1, pSeg);
+ if( p1->base.nData ){
+ xAppend(p, p1->base.iRowid-iLastRowid, p1, &doclist);
+ iLastRowid = p1->base.iRowid;
+ }
+ }
+ fts5MultiIterFree(p1);
+ }
+
+ pToken[0] = FTS5_MAIN_PREFIX + iIdx;
fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1);
fts5IterSetOutputCb(&p->rc, p1);
for( /* no-op */ ;
@@ -221487,13 +226253,21 @@ static void fts5SetupPrefixIter(
if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){
for(i=0; p->rc==SQLITE_OK && doclist.n; i++){
- assert( i<nBuf );
- if( aBuf[i].n==0 ){
- fts5BufferSwap(&doclist, &aBuf[i]);
- fts5BufferZero(&doclist);
- }else{
- xMerge(p, &doclist, &aBuf[i]);
- fts5BufferZero(&aBuf[i]);
+ int i1 = i*nMerge;
+ int iStore;
+ assert( i1+nMerge<=nBuf );
+ for(iStore=i1; iStore<i1+nMerge; iStore++){
+ if( aBuf[iStore].n==0 ){
+ fts5BufferSwap(&doclist, &aBuf[iStore]);
+ fts5BufferZero(&doclist);
+ break;
+ }
+ }
+ if( iStore==i1+nMerge ){
+ xMerge(p, &doclist, nMerge, &aBuf[i1]);
+ for(iStore=i1; iStore<i1+nMerge; iStore++){
+ fts5BufferZero(&aBuf[iStore]);
+ }
}
}
iLastRowid = 0;
@@ -221503,11 +226277,15 @@ static void fts5SetupPrefixIter(
iLastRowid = p1->base.iRowid;
}
- for(i=0; i<nBuf; i++){
+ assert( (nBuf%nMerge)==0 );
+ for(i=0; i<nBuf; i+=nMerge){
+ int iFree;
if( p->rc==SQLITE_OK ){
- xMerge(p, &doclist, &aBuf[i]);
+ xMerge(p, &doclist, nMerge, &aBuf[i]);
+ }
+ for(iFree=i; iFree<i+nMerge; iFree++){
+ fts5BufferFree(&aBuf[iFree]);
}
- fts5BufferFree(&aBuf[i]);
}
fts5MultiIterFree(p1);
@@ -221762,6 +226540,7 @@ static int sqlite3Fts5IndexQuery(
if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){
int iIdx = 0; /* Index to search */
+ int iPrefixIdx = 0; /* +1 prefix index */
if( nToken ) memcpy(&buf.p[1], pToken, nToken);
/* Figure out which index to search and set iIdx accordingly. If this
@@ -221783,7 +226562,9 @@ static int sqlite3Fts5IndexQuery(
if( flags & FTS5INDEX_QUERY_PREFIX ){
int nChar = fts5IndexCharlen(pToken, nToken);
for(iIdx=1; iIdx<=pConfig->nPrefix; iIdx++){
- if( pConfig->aPrefix[iIdx-1]==nChar ) break;
+ int nIdxChar = pConfig->aPrefix[iIdx-1];
+ if( nIdxChar==nChar ) break;
+ if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx;
}
}
@@ -221800,8 +226581,7 @@ static int sqlite3Fts5IndexQuery(
}else{
/* Scan multiple terms in the main index */
int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0;
- buf.p[0] = FTS5_MAIN_PREFIX;
- fts5SetupPrefixIter(p, bDesc, buf.p, nToken+1, pColset, &pRet);
+ fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet);
assert( p->rc!=SQLITE_OK || pRet->pColset==0 );
fts5IterSetOutputCb(&p->rc, pRet);
if( p->rc==SQLITE_OK ){
@@ -221874,8 +226654,9 @@ static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){
static const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){
int n;
const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n);
+ assert_nc( z || n<=1 );
*pn = n-1;
- return &z[1];
+ return (z ? &z[1] : 0);
}
/*
@@ -222412,7 +227193,7 @@ static void fts5IndexIntegrityCheckSegment(
** error, or some other SQLite error code if another error (e.g. OOM)
** occurs.
*/
-static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
+static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){
int eDetail = p->pConfig->eDetail;
u64 cksum2 = 0; /* Checksum based on contents of indexes */
Fts5Buffer poslist = {0,0,0}; /* Buffer used to hold a poslist */
@@ -222473,6 +227254,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
}else{
poslist.n = 0;
fts5SegiterPoslist(p, &pIter->aSeg[pIter->aFirst[1].iFirst], 0, &poslist);
+ fts5BufferAppendBlob(&p->rc, &poslist, 4, (const u8*)"\0\0\0\0");
while( 0==sqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){
int iCol = FTS5_POS2COLUMN(iPos);
int iTokOff = FTS5_POS2OFFSET(iPos);
@@ -222483,7 +227265,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3);
fts5MultiIterFree(pIter);
- if( p->rc==SQLITE_OK && cksum!=cksum2 ) p->rc = FTS5_CORRUPT;
+ if( p->rc==SQLITE_OK && bUseCksum && cksum!=cksum2 ) p->rc = FTS5_CORRUPT;
fts5StructureRelease(pStruct);
#ifdef SQLITE_DEBUG
@@ -222499,6 +227281,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
** function only.
*/
+#ifdef SQLITE_TEST
/*
** Decode a segment-data rowid from the %_data table. This function is
** the opposite of macro FTS5_SEGMENT_ROWID().
@@ -222521,7 +227304,9 @@ static void fts5DecodeRowid(
*piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1));
}
+#endif /* SQLITE_TEST */
+#ifdef SQLITE_TEST
static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){
int iSegid, iHeight, iPgno, bDlidx; /* Rowid compenents */
fts5DecodeRowid(iKey, &iSegid, &bDlidx, &iHeight, &iPgno);
@@ -222539,7 +227324,9 @@ static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){
);
}
}
+#endif /* SQLITE_TEST */
+#ifdef SQLITE_TEST
static void fts5DebugStructure(
int *pRc, /* IN/OUT: error code */
Fts5Buffer *pBuf,
@@ -222561,7 +227348,9 @@ static void fts5DebugStructure(
sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}");
}
}
+#endif /* SQLITE_TEST */
+#ifdef SQLITE_TEST
/*
** This is part of the fts5_decode() debugging aid.
**
@@ -222586,7 +227375,9 @@ static void fts5DecodeStructure(
fts5DebugStructure(pRc, pBuf, p);
fts5StructureRelease(p);
}
+#endif /* SQLITE_TEST */
+#ifdef SQLITE_TEST
/*
** This is part of the fts5_decode() debugging aid.
**
@@ -222609,7 +227400,9 @@ static void fts5DecodeAverages(
zSpace = " ";
}
}
+#endif /* SQLITE_TEST */
+#ifdef SQLITE_TEST
/*
** Buffer (a/n) is assumed to contain a list of serialized varints. Read
** each varint and append its string representation to buffer pBuf. Return
@@ -222626,7 +227419,9 @@ static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){
}
return iOff;
}
+#endif /* SQLITE_TEST */
+#ifdef SQLITE_TEST
/*
** The start of buffer (a/n) contains the start of a doclist. The doclist
** may or may not finish within the buffer. This function appends a text
@@ -222659,7 +227454,9 @@ static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){
return iOff;
}
+#endif /* SQLITE_TEST */
+#ifdef SQLITE_TEST
/*
** This function is part of the fts5_decode() debugging function. It is
** only ever used with detail=none tables.
@@ -222700,7 +227497,9 @@ static void fts5DecodeRowidList(
sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp);
}
}
+#endif /* SQLITE_TEST */
+#ifdef SQLITE_TEST
/*
** The implementation of user-defined scalar function fts5_decode().
*/
@@ -222909,7 +227708,9 @@ static void fts5DecodeFunction(
}
fts5BufferFree(&s);
}
+#endif /* SQLITE_TEST */
+#ifdef SQLITE_TEST
/*
** The implementation of user-defined scalar function fts5_rowid().
*/
@@ -222943,6 +227744,7 @@ static void fts5RowidFunction(
}
}
}
+#endif /* SQLITE_TEST */
/*
** This is called as part of registering the FTS5 module with database
@@ -222953,6 +227755,7 @@ static void fts5RowidFunction(
** SQLite error code is returned instead.
*/
static int sqlite3Fts5IndexInit(sqlite3 *db){
+#ifdef SQLITE_TEST
int rc = sqlite3_create_function(
db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0
);
@@ -222970,6 +227773,10 @@ static int sqlite3Fts5IndexInit(sqlite3 *db){
);
}
return rc;
+#else
+ return SQLITE_OK;
+ UNUSED_PARAM(db);
+#endif
}
@@ -223005,7 +227812,9 @@ static int sqlite3Fts5IndexReset(Fts5Index *p){
** assert() conditions in the fts5 code are activated - conditions that are
** only true if it is guaranteed that the fts5 database is not corrupt.
*/
+#ifdef SQLITE_DEBUG
SQLITE_API int sqlite3_fts5_may_be_corrupt = 1;
+#endif
typedef struct Fts5Auxdata Fts5Auxdata;
@@ -223447,6 +228256,23 @@ static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){
#endif
}
+static int fts5UsePatternMatch(
+ Fts5Config *pConfig,
+ struct sqlite3_index_constraint *p
+){
+ assert( FTS5_PATTERN_GLOB==SQLITE_INDEX_CONSTRAINT_GLOB );
+ assert( FTS5_PATTERN_LIKE==SQLITE_INDEX_CONSTRAINT_LIKE );
+ if( pConfig->ePattern==FTS5_PATTERN_GLOB && p->op==FTS5_PATTERN_GLOB ){
+ return 1;
+ }
+ if( pConfig->ePattern==FTS5_PATTERN_LIKE
+ && (p->op==FTS5_PATTERN_LIKE || p->op==FTS5_PATTERN_GLOB)
+ ){
+ return 1;
+ }
+ return 0;
+}
+
/*
** Implementation of the xBestIndex method for FTS5 tables. Within the
** WHERE constraint, it searches for the following:
@@ -223476,7 +228302,9 @@ static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){
**
** Match against table column: "m"
** Match against rank column: "r"
-** Match against other column: "<column-number>"
+** Match against other column: "M<column-number>"
+** LIKE against other column: "L<column-number>"
+** GLOB against other column: "G<column-number>"
** Equality constraint against the rowid: "="
** A < or <= against the rowid: "<"
** A > or >= against the rowid: ">"
@@ -223537,7 +228365,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
return SQLITE_ERROR;
}
- idxStr = (char*)sqlite3_malloc(pInfo->nConstraint * 6 + 1);
+ idxStr = (char*)sqlite3_malloc(pInfo->nConstraint * 8 + 1);
if( idxStr==0 ) return SQLITE_NOMEM;
pInfo->idxStr = idxStr;
pInfo->needToFreeIdxStr = 1;
@@ -223561,25 +228389,29 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
if( bSeenRank ) continue;
idxStr[iIdxStr++] = 'r';
bSeenRank = 1;
- }else{
+ }else if( iCol>=0 ){
bSeenMatch = 1;
- idxStr[iIdxStr++] = 'm';
- if( iCol<nCol ){
- sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol);
- idxStr += strlen(&idxStr[iIdxStr]);
- assert( idxStr[iIdxStr]=='\0' );
- }
+ idxStr[iIdxStr++] = 'M';
+ sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol);
+ idxStr += strlen(&idxStr[iIdxStr]);
+ assert( idxStr[iIdxStr]=='\0' );
}
pInfo->aConstraintUsage[i].argvIndex = ++iCons;
pInfo->aConstraintUsage[i].omit = 1;
}
- }
- else if( p->usable && bSeenEq==0
- && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0
- ){
- idxStr[iIdxStr++] = '=';
- bSeenEq = 1;
- pInfo->aConstraintUsage[i].argvIndex = ++iCons;
+ }else if( p->usable ){
+ if( iCol>=0 && iCol<nCol && fts5UsePatternMatch(pConfig, p) ){
+ assert( p->op==FTS5_PATTERN_LIKE || p->op==FTS5_PATTERN_GLOB );
+ idxStr[iIdxStr++] = p->op==FTS5_PATTERN_LIKE ? 'L' : 'G';
+ sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol);
+ idxStr += strlen(&idxStr[iIdxStr]);
+ pInfo->aConstraintUsage[i].argvIndex = ++iCons;
+ assert( idxStr[iIdxStr]=='\0' );
+ }else if( bSeenEq==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0 ){
+ idxStr[iIdxStr++] = '=';
+ bSeenEq = 1;
+ pInfo->aConstraintUsage[i].argvIndex = ++iCons;
+ }
}
}
@@ -224212,19 +229044,14 @@ static int fts5FilterMethod(
case 'r':
pRank = apVal[i];
break;
- case 'm': {
+ case 'M': {
const char *zText = (const char*)sqlite3_value_text(apVal[i]);
if( zText==0 ) zText = "";
-
- if( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' ){
- iCol = 0;
- do{
- iCol = iCol*10 + (idxStr[iIdxStr]-'0');
- iIdxStr++;
- }while( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' );
- }else{
- iCol = pConfig->nCol;
- }
+ iCol = 0;
+ do{
+ iCol = iCol*10 + (idxStr[iIdxStr]-'0');
+ iIdxStr++;
+ }while( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' );
if( zText[0]=='*' ){
/* The user has issued a query of the form "MATCH '*...'". This
@@ -224234,7 +229061,7 @@ static int fts5FilterMethod(
goto filter_out;
}else{
char **pzErr = &pTab->p.base.zErrMsg;
- rc = sqlite3Fts5ExprNew(pConfig, iCol, zText, &pExpr, pzErr);
+ rc = sqlite3Fts5ExprNew(pConfig, 0, iCol, zText, &pExpr, pzErr);
if( rc==SQLITE_OK ){
rc = sqlite3Fts5ExprAnd(&pCsr->pExpr, pExpr);
pExpr = 0;
@@ -224244,6 +229071,25 @@ static int fts5FilterMethod(
break;
}
+ case 'L':
+ case 'G': {
+ int bGlob = (idxStr[iIdxStr-1]=='G');
+ const char *zText = (const char*)sqlite3_value_text(apVal[i]);
+ iCol = 0;
+ do{
+ iCol = iCol*10 + (idxStr[iIdxStr]-'0');
+ iIdxStr++;
+ }while( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' );
+ if( zText ){
+ rc = sqlite3Fts5ExprPattern(pConfig, bGlob, iCol, zText, &pExpr);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5ExprAnd(&pCsr->pExpr, pExpr);
+ pExpr = 0;
+ }
+ if( rc!=SQLITE_OK ) goto filter_out;
+ break;
+ }
case '=':
pRowidEq = apVal[i];
break;
@@ -224491,7 +229337,8 @@ static int fts5SpecialInsert(
int nMerge = sqlite3_value_int(pVal);
rc = sqlite3Fts5StorageMerge(pTab->pStorage, nMerge);
}else if( 0==sqlite3_stricmp("integrity-check", zCmd) ){
- rc = sqlite3Fts5StorageIntegrity(pTab->pStorage);
+ int iArg = sqlite3_value_int(pVal);
+ rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, iArg);
#ifdef SQLITE_DEBUG
}else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){
pConfig->bPrefixIndex = sqlite3_value_int(pVal);
@@ -224892,13 +229739,15 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){
nInst++;
if( nInst>=pCsr->nInstAlloc ){
- pCsr->nInstAlloc = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32;
+ int nNewSize = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32;
aInst = (int*)sqlite3_realloc64(
- pCsr->aInst, pCsr->nInstAlloc*sizeof(int)*3
+ pCsr->aInst, nNewSize*sizeof(int)*3
);
if( aInst ){
pCsr->aInst = aInst;
+ pCsr->nInstAlloc = nNewSize;
}else{
+ nInst--;
rc = SQLITE_NOMEM;
break;
}
@@ -225122,7 +229971,8 @@ static int fts5ApiPhraseFirst(
int n;
int rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n);
if( rc==SQLITE_OK ){
- pIter->b = &pIter->a[n];
+ assert( pIter->a || n==0 );
+ pIter->b = (pIter->a ? &pIter->a[n] : 0);
*piCol = 0;
*piOff = 0;
fts5ApiPhraseNext(pCtx, pIter, piCol, piOff);
@@ -225181,7 +230031,8 @@ static int fts5ApiPhraseFirstColumn(
rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n);
}
if( rc==SQLITE_OK ){
- pIter->b = &pIter->a[n];
+ assert( pIter->a || n==0 );
+ pIter->b = (pIter->a ? &pIter->a[n] : 0);
*piCol = 0;
fts5ApiPhraseNextColumn(pCtx, pIter, piCol);
}
@@ -225189,7 +230040,8 @@ static int fts5ApiPhraseFirstColumn(
int n;
rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n);
if( rc==SQLITE_OK ){
- pIter->b = &pIter->a[n];
+ assert( pIter->a || n==0 );
+ pIter->b = (pIter->a ? &pIter->a[n] : 0);
if( n<=0 ){
*piCol = -1;
}else if( pIter->a[0]==0x01 ){
@@ -225654,8 +230506,7 @@ static int sqlite3Fts5GetTokenizer(
Fts5Global *pGlobal,
const char **azArg,
int nArg,
- Fts5Tokenizer **ppTok,
- fts5_tokenizer **ppTokApi,
+ Fts5Config *pConfig,
char **pzErr
){
Fts5TokenizerModule *pMod;
@@ -225667,16 +230518,22 @@ static int sqlite3Fts5GetTokenizer(
rc = SQLITE_ERROR;
*pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]);
}else{
- rc = pMod->x.xCreate(pMod->pUserData, &azArg[1], (nArg?nArg-1:0), ppTok);
- *ppTokApi = &pMod->x;
- if( rc!=SQLITE_OK && pzErr ){
- *pzErr = sqlite3_mprintf("error in tokenizer constructor");
+ rc = pMod->x.xCreate(
+ pMod->pUserData, (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->pTok
+ );
+ pConfig->pTokApi = &pMod->x;
+ if( rc!=SQLITE_OK ){
+ if( pzErr ) *pzErr = sqlite3_mprintf("error in tokenizer constructor");
+ }else{
+ pConfig->ePattern = sqlite3Fts5TokenizerPattern(
+ pMod->x.xCreate, pConfig->pTok
+ );
}
}
if( rc!=SQLITE_OK ){
- *ppTokApi = 0;
- *ppTok = 0;
+ pConfig->pTokApi = 0;
+ pConfig->pTok = 0;
}
return rc;
@@ -225725,7 +230582,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
- sqlite3_result_text(pCtx, "fts5: 2020-08-14 13:23:32 fca8dc8b578f215a969cd899336378966156154710873e68b3d9ac5881b0ff3f", -1, SQLITE_TRANSIENT);
+ sqlite3_result_text(pCtx, "fts5: 2021-06-18 18:36:39 5c9a6c06871cb9fe42814af9c039eb6da5427a6ec28f187af7ebfb62eafa66e5", -1, SQLITE_TRANSIENT);
}
/*
@@ -226288,9 +231145,16 @@ static int fts5StorageDeleteFromIndex(
zText, nText, (void*)&ctx, fts5StorageInsertCallback
);
p->aTotalSize[iCol-1] -= (i64)ctx.szCol;
+ if( p->aTotalSize[iCol-1]<0 ){
+ rc = FTS5_CORRUPT;
+ }
}
}
- p->nTotalRow--;
+ if( rc==SQLITE_OK && p->nTotalRow<1 ){
+ rc = FTS5_CORRUPT;
+ }else{
+ p->nTotalRow--;
+ }
rc2 = sqlite3_reset(pSeek);
if( rc==SQLITE_OK ) rc = rc2;
@@ -226733,13 +231597,14 @@ static int fts5StorageIntegrityCallback(
** some other SQLite error code if an error occurs while attempting to
** determine this.
*/
-static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
+static int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg){
Fts5Config *pConfig = p->pConfig;
- int rc; /* Return code */
+ int rc = SQLITE_OK; /* Return code */
int *aColSize; /* Array of size pConfig->nCol */
i64 *aTotalSize; /* Array of size pConfig->nCol */
Fts5IntegrityCtx ctx;
sqlite3_stmt *pScan;
+ int bUseCksum;
memset(&ctx, 0, sizeof(Fts5IntegrityCtx));
ctx.pConfig = p->pConfig;
@@ -226748,83 +231613,88 @@ static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
aColSize = (int*)&aTotalSize[pConfig->nCol];
memset(aTotalSize, 0, sizeof(i64) * pConfig->nCol);
- /* Generate the expected index checksum based on the contents of the
- ** %_content table. This block stores the checksum in ctx.cksum. */
- rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0);
- if( rc==SQLITE_OK ){
- int rc2;
- while( SQLITE_ROW==sqlite3_step(pScan) ){
- int i;
- ctx.iRowid = sqlite3_column_int64(pScan, 0);
- ctx.szCol = 0;
- if( pConfig->bColumnsize ){
- rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize);
- }
- if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_NONE ){
- rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
- }
- for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
- if( pConfig->abUnindexed[i] ) continue;
- ctx.iCol = i;
+ bUseCksum = (pConfig->eContent==FTS5_CONTENT_NORMAL
+ || (pConfig->eContent==FTS5_CONTENT_EXTERNAL && iArg)
+ );
+ if( bUseCksum ){
+ /* Generate the expected index checksum based on the contents of the
+ ** %_content table. This block stores the checksum in ctx.cksum. */
+ rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0);
+ if( rc==SQLITE_OK ){
+ int rc2;
+ while( SQLITE_ROW==sqlite3_step(pScan) ){
+ int i;
+ ctx.iRowid = sqlite3_column_int64(pScan, 0);
ctx.szCol = 0;
- if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
- rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
- }
- if( rc==SQLITE_OK ){
- const char *zText = (const char*)sqlite3_column_text(pScan, i+1);
- int nText = sqlite3_column_bytes(pScan, i+1);
- rc = sqlite3Fts5Tokenize(pConfig,
- FTS5_TOKENIZE_DOCUMENT,
- zText, nText,
- (void*)&ctx,
- fts5StorageIntegrityCallback
- );
+ if( pConfig->bColumnsize ){
+ rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize);
}
- if( rc==SQLITE_OK && pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){
- rc = FTS5_CORRUPT;
+ if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_NONE ){
+ rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
}
- aTotalSize[i] += ctx.szCol;
- if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
- sqlite3Fts5TermsetFree(ctx.pTermset);
- ctx.pTermset = 0;
+ for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
+ if( pConfig->abUnindexed[i] ) continue;
+ ctx.iCol = i;
+ ctx.szCol = 0;
+ if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
+ rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
+ }
+ if( rc==SQLITE_OK ){
+ const char *zText = (const char*)sqlite3_column_text(pScan, i+1);
+ int nText = sqlite3_column_bytes(pScan, i+1);
+ rc = sqlite3Fts5Tokenize(pConfig,
+ FTS5_TOKENIZE_DOCUMENT,
+ zText, nText,
+ (void*)&ctx,
+ fts5StorageIntegrityCallback
+ );
+ }
+ if( rc==SQLITE_OK && pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){
+ rc = FTS5_CORRUPT;
+ }
+ aTotalSize[i] += ctx.szCol;
+ if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
+ sqlite3Fts5TermsetFree(ctx.pTermset);
+ ctx.pTermset = 0;
+ }
}
- }
- sqlite3Fts5TermsetFree(ctx.pTermset);
- ctx.pTermset = 0;
+ sqlite3Fts5TermsetFree(ctx.pTermset);
+ ctx.pTermset = 0;
- if( rc!=SQLITE_OK ) break;
+ if( rc!=SQLITE_OK ) break;
+ }
+ rc2 = sqlite3_reset(pScan);
+ if( rc==SQLITE_OK ) rc = rc2;
}
- rc2 = sqlite3_reset(pScan);
- if( rc==SQLITE_OK ) rc = rc2;
- }
- /* Test that the "totals" (sometimes called "averages") record looks Ok */
- if( rc==SQLITE_OK ){
- int i;
- rc = fts5StorageLoadTotals(p, 0);
- for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
- if( p->aTotalSize[i]!=aTotalSize[i] ) rc = FTS5_CORRUPT;
+ /* Test that the "totals" (sometimes called "averages") record looks Ok */
+ if( rc==SQLITE_OK ){
+ int i;
+ rc = fts5StorageLoadTotals(p, 0);
+ for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
+ if( p->aTotalSize[i]!=aTotalSize[i] ) rc = FTS5_CORRUPT;
+ }
}
- }
- /* Check that the %_docsize and %_content tables contain the expected
- ** number of rows. */
- if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
- i64 nRow = 0;
- rc = fts5StorageCount(p, "content", &nRow);
- if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
- }
- if( rc==SQLITE_OK && pConfig->bColumnsize ){
- i64 nRow = 0;
- rc = fts5StorageCount(p, "docsize", &nRow);
- if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
+ /* Check that the %_docsize and %_content tables contain the expected
+ ** number of rows. */
+ if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
+ i64 nRow = 0;
+ rc = fts5StorageCount(p, "content", &nRow);
+ if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
+ }
+ if( rc==SQLITE_OK && pConfig->bColumnsize ){
+ i64 nRow = 0;
+ rc = fts5StorageCount(p, "docsize", &nRow);
+ if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
+ }
}
/* Pass the expected checksum down to the FTS index module. It will
** verify, amongst other things, that it matches the checksum generated by
** inspecting the index itself. */
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum);
+ rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum, bUseCksum);
}
sqlite3_free(aTotalSize);
@@ -228266,6 +233136,133 @@ static int fts5PorterTokenize(
);
}
+/**************************************************************************
+** Start of trigram implementation.
+*/
+typedef struct TrigramTokenizer TrigramTokenizer;
+struct TrigramTokenizer {
+ int bFold; /* True to fold to lower-case */
+};
+
+/*
+** Free a trigram tokenizer.
+*/
+static void fts5TriDelete(Fts5Tokenizer *p){
+ sqlite3_free(p);
+}
+
+/*
+** Allocate a trigram tokenizer.
+*/
+static int fts5TriCreate(
+ void *pUnused,
+ const char **azArg,
+ int nArg,
+ Fts5Tokenizer **ppOut
+){
+ int rc = SQLITE_OK;
+ TrigramTokenizer *pNew = (TrigramTokenizer*)sqlite3_malloc(sizeof(*pNew));
+ UNUSED_PARAM(pUnused);
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ int i;
+ pNew->bFold = 1;
+ for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
+ const char *zArg = azArg[i+1];
+ if( 0==sqlite3_stricmp(azArg[i], "case_sensitive") ){
+ if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1] ){
+ rc = SQLITE_ERROR;
+ }else{
+ pNew->bFold = (zArg[0]=='0');
+ }
+ }else{
+ rc = SQLITE_ERROR;
+ }
+ }
+ if( rc!=SQLITE_OK ){
+ fts5TriDelete((Fts5Tokenizer*)pNew);
+ pNew = 0;
+ }
+ }
+ *ppOut = (Fts5Tokenizer*)pNew;
+ return rc;
+}
+
+/*
+** Trigram tokenizer tokenize routine.
+*/
+static int fts5TriTokenize(
+ Fts5Tokenizer *pTok,
+ void *pCtx,
+ int unusedFlags,
+ const char *pText, int nText,
+ int (*xToken)(void*, int, const char*, int, int, int)
+){
+ TrigramTokenizer *p = (TrigramTokenizer*)pTok;
+ int rc = SQLITE_OK;
+ char aBuf[32];
+ const unsigned char *zIn = (const unsigned char*)pText;
+ const unsigned char *zEof = &zIn[nText];
+ u32 iCode;
+
+ UNUSED_PARAM(unusedFlags);
+ while( 1 ){
+ char *zOut = aBuf;
+ int iStart = zIn - (const unsigned char*)pText;
+ const unsigned char *zNext;
+
+ READ_UTF8(zIn, zEof, iCode);
+ if( iCode==0 ) break;
+ zNext = zIn;
+ if( zIn<zEof ){
+ if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0);
+ WRITE_UTF8(zOut, iCode);
+ READ_UTF8(zIn, zEof, iCode);
+ if( iCode==0 ) break;
+ }else{
+ break;
+ }
+ if( zIn<zEof ){
+ if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0);
+ WRITE_UTF8(zOut, iCode);
+ READ_UTF8(zIn, zEof, iCode);
+ if( iCode==0 ) break;
+ if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, 0);
+ WRITE_UTF8(zOut, iCode);
+ }else{
+ break;
+ }
+ rc = xToken(pCtx, 0, aBuf, zOut-aBuf, iStart, iStart + zOut-aBuf);
+ if( rc!=SQLITE_OK ) break;
+ zIn = zNext;
+ }
+
+ return rc;
+}
+
+/*
+** Argument xCreate is a pointer to a constructor function for a tokenizer.
+** pTok is a tokenizer previously created using the same method. This function
+** returns one of FTS5_PATTERN_NONE, FTS5_PATTERN_LIKE or FTS5_PATTERN_GLOB
+** indicating the style of pattern matching that the tokenizer can support.
+** In practice, this is:
+**
+** "trigram" tokenizer, case_sensitive=1 - FTS5_PATTERN_GLOB
+** "trigram" tokenizer, case_sensitive=0 (the default) - FTS5_PATTERN_LIKE
+** all other tokenizers - FTS5_PATTERN_NONE
+*/
+static int sqlite3Fts5TokenizerPattern(
+ int (*xCreate)(void*, const char**, int, Fts5Tokenizer**),
+ Fts5Tokenizer *pTok
+){
+ if( xCreate==fts5TriCreate ){
+ TrigramTokenizer *p = (TrigramTokenizer*)pTok;
+ return p->bFold ? FTS5_PATTERN_LIKE : FTS5_PATTERN_GLOB;
+ }
+ return FTS5_PATTERN_NONE;
+}
+
/*
** Register all built-in tokenizers with FTS5.
*/
@@ -228277,6 +233274,7 @@ static int sqlite3Fts5TokenizerInit(fts5_api *pApi){
{ "unicode61", {fts5UnicodeCreate, fts5UnicodeDelete, fts5UnicodeTokenize}},
{ "ascii", {fts5AsciiCreate, fts5AsciiDelete, fts5AsciiTokenize }},
{ "porter", {fts5PorterCreate, fts5PorterDelete, fts5PorterTokenize }},
+ { "trigram", {fts5TriCreate, fts5TriDelete, fts5TriTokenize}},
};
int rc = SQLITE_OK; /* Return code */
@@ -229069,8 +234067,10 @@ static void sqlite3Fts5UnicodeAscii(u8 *aArray, u8 *aAscii){
}
iTbl++;
}
+ aAscii[0] = 0; /* 0x00 is never a token character */
}
+
/*
** 2015 May 30
**
@@ -230508,9 +235508,9 @@ SQLITE_API int sqlite3_stmt_init(
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
/************** End of stmt.c ************************************************/
-#if __LINE__!=230511
+#if __LINE__!=235511
#undef SQLITE_SOURCE_ID
-#define SQLITE_SOURCE_ID "2020-08-14 13:23:32 fca8dc8b578f215a969cd899336378966156154710873e68b3d9ac5881b0alt2"
+#define SQLITE_SOURCE_ID "2021-06-18 18:36:39 5c9a6c06871cb9fe42814af9c039eb6da5427a6ec28f187af7ebfb62eafaalt2"
#endif
/* Return the source-id for this library */
SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
diff --git a/db/sqlite3/src/sqlite3.h b/db/sqlite3/src/sqlite3.h
index 910b687aa7..3274bbe071 100644
--- a/db/sqlite3/src/sqlite3.h
+++ b/db/sqlite3/src/sqlite3.h
@@ -123,9 +123,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.33.0"
-#define SQLITE_VERSION_NUMBER 3033000
-#define SQLITE_SOURCE_ID "2020-08-14 13:23:32 fca8dc8b578f215a969cd899336378966156154710873e68b3d9ac5881b0ff3f"
+#define SQLITE_VERSION "3.36.0"
+#define SQLITE_VERSION_NUMBER 3036000
+#define SQLITE_SOURCE_ID "2021-06-18 18:36:39 5c9a6c06871cb9fe42814af9c039eb6da5427a6ec28f187af7ebfb62eafa66e5"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -504,6 +504,7 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8))
#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8))
#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8))
+#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
@@ -1127,6 +1128,23 @@ struct sqlite3_io_methods {
** file to the database file, but before the *-shm file is updated to
** record the fact that the pages have been checkpointed.
** </ul>
+**
+** <li>[[SQLITE_FCNTL_EXTERNAL_READER]]
+** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect
+** whether or not there is a database client in another process with a wal-mode
+** transaction open on the database or not. It is only available on unix.The
+** (void*) argument passed with this file-control should be a pointer to a
+** value of type (int). The integer value is set to 1 if the database is a wal
+** mode database and there exists at least one client in another process that
+** currently has an SQL transaction open on the database. It is set to 0 if
+** the database is not a wal-mode db, or if there is no such connection in any
+** other process. This opcode cannot be used to detect transactions opened
+** by clients within the current process, only within other processes.
+** </ul>
+**
+** <li>[[SQLITE_FCNTL_CKSM_FILE]]
+** Used by the cksmvfs VFS module only.
+** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
#define SQLITE_FCNTL_GET_LOCKPROXYFILE 2
@@ -1166,6 +1184,8 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CKPT_DONE 37
#define SQLITE_FCNTL_RESERVE_BYTES 38
#define SQLITE_FCNTL_CKPT_START 39
+#define SQLITE_FCNTL_EXTERNAL_READER 40
+#define SQLITE_FCNTL_CKSM_FILE 41
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -2114,7 +2134,13 @@ struct sqlite3_mem_methods {
** The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether triggers are disabled or enabled
** following this call. The second parameter may be a NULL pointer, in
-** which case the trigger setting is not reported back. </dd>
+** which case the trigger setting is not reported back.
+**
+** <p>Originally this option disabled all triggers. ^(However, since
+** SQLite version 3.35.0, TEMP triggers are still allowed even if
+** this option is off. So, in other words, this option now only disables
+** triggers in the main database schema or in the schemas of ATTACH-ed
+** databases.)^ </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt>
@@ -2125,7 +2151,13 @@ struct sqlite3_mem_methods {
** The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether views are disabled or enabled
** following this call. The second parameter may be a NULL pointer, in
-** which case the view setting is not reported back. </dd>
+** which case the view setting is not reported back.
+**
+** <p>Originally this option disabled all views. ^(However, since
+** SQLite version 3.35.0, TEMP views are still allowed even if
+** this option is off. So, in other words, this option now only disables
+** views in the main database schema or in the schemas of ATTACH-ed
+** databases.)^ </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
@@ -3498,6 +3530,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** that uses dot-files in place of posix advisory locking.
** <tr><td> file:data.db?mode=readonly <td>
** An error. "readonly" is not a valid option for the "mode" parameter.
+** Use "ro" instead: "file:data.db?mode=ro".
** </table>
**
** ^URI hexadecimal escape sequences (%HH) are supported within the path and
@@ -3696,7 +3729,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
** If the Y parameter to sqlite3_free_filename(Y) is anything other
** than a NULL pointer or a pointer previously acquired from
** sqlite3_create_filename(), then bad things such as heap
-** corruption or segfaults may occur. The value Y should be
+** corruption or segfaults may occur. The value Y should not be
** used again after sqlite3_free_filename(Y) has been called. This means
** that if the [sqlite3_vfs.xOpen()] method of a VFS has been called using Y,
** then the corresponding [sqlite3_module.xClose() method should also be
@@ -4165,6 +4198,15 @@ SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt);
** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and
** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so
** sqlite3_stmt_readonly() returns false for those commands.
+**
+** ^This routine returns false if there is any possibility that the
+** statement might change the database file. ^A false return does
+** not guarantee that the statement will change the database file.
+** ^For example, an UPDATE statement might have a WHERE clause that
+** makes it a no-op, but the sqlite3_stmt_readonly() result would still
+** be false. ^Similarly, a CREATE TABLE IF NOT EXISTS statement is a
+** read-only no-op if the table already exists, but
+** sqlite3_stmt_readonly() still returns false for such a statement.
*/
SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
@@ -4334,18 +4376,22 @@ typedef struct sqlite3_context sqlite3_context;
** contain embedded NULs. The result of expressions involving strings
** with embedded NULs is undefined.
**
-** ^The fifth argument to the BLOB and string binding interfaces
-** is a destructor used to dispose of the BLOB or
-** string after SQLite has finished with it. ^The destructor is called
-** to dispose of the BLOB or string even if the call to the bind API fails,
-** except the destructor is not called if the third parameter is a NULL
-** pointer or the fourth parameter is negative.
-** ^If the fifth argument is
-** the special value [SQLITE_STATIC], then SQLite assumes that the
-** information is in static, unmanaged space and does not need to be freed.
-** ^If the fifth argument has the value [SQLITE_TRANSIENT], then
-** SQLite makes its own private copy of the data immediately, before
-** the sqlite3_bind_*() routine returns.
+** ^The fifth argument to the BLOB and string binding interfaces controls
+** or indicates the lifetime of the object referenced by the third parameter.
+** These three options exist:
+** ^ (1) A destructor to dispose of the BLOB or string after SQLite has finished
+** with it may be passed. ^It is called to dispose of the BLOB or string even
+** if the call to the bind API fails, except the destructor is not called if
+** the third parameter is a NULL pointer or the fourth parameter is negative.
+** ^ (2) The special constant, [SQLITE_STATIC], may be passsed to indicate that
+** the application remains responsible for disposing of the object. ^In this
+** case, the object and the provided pointer to it must remain valid until
+** either the prepared statement is finalized or the same SQL parameter is
+** bound to something else, whichever occurs sooner.
+** ^ (3) The constant, [SQLITE_TRANSIENT], may be passed to indicate that the
+** object is to be copied prior to the return from sqlite3_bind_*(). ^The
+** object and pointer to it must remain valid until then. ^SQLite will then
+** manage the lifetime of its private copy.
**
** ^The sixth argument to sqlite3_bind_text64() must be one of
** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]
@@ -5087,7 +5133,6 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions,
** index expressions, or the WHERE clause of partial indexes.
**
-** <span style="background-color:#ffff90;">
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
** all application-defined SQL functions that do not need to be
** used inside of triggers, view, CHECK constraints, or other elements of
@@ -5097,7 +5142,6 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** a database file to include invocations of the function with parameters
** chosen by the attacker, which the application will then execute when
** the database file is opened and read.
-** </span>
**
** ^(The fifth parameter is an arbitrary pointer. The implementation of the
** function can gain access to this pointer using [sqlite3_user_data()].)^
@@ -6187,6 +6231,57 @@ SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName);
SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName);
/*
+** CAPI3REF: Determine the transaction state of a database
+** METHOD: sqlite3
+**
+** ^The sqlite3_txn_state(D,S) interface returns the current
+** [transaction state] of schema S in database connection D. ^If S is NULL,
+** then the highest transaction state of any schema on database connection D
+** is returned. Transaction states are (in order of lowest to highest):
+** <ol>
+** <li value="0"> SQLITE_TXN_NONE
+** <li value="1"> SQLITE_TXN_READ
+** <li value="2"> SQLITE_TXN_WRITE
+** </ol>
+** ^If the S argument to sqlite3_txn_state(D,S) is not the name of
+** a valid schema, then -1 is returned.
+*/
+SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
+
+/*
+** CAPI3REF: Allowed return values from [sqlite3_txn_state()]
+** KEYWORDS: {transaction state}
+**
+** These constants define the current transaction state of a database file.
+** ^The [sqlite3_txn_state(D,S)] interface returns one of these
+** constants in order to describe the transaction state of schema S
+** in [database connection] D.
+**
+** <dl>
+** [[SQLITE_TXN_NONE]] <dt>SQLITE_TXN_NONE</dt>
+** <dd>The SQLITE_TXN_NONE state means that no transaction is currently
+** pending.</dd>
+**
+** [[SQLITE_TXN_READ]] <dt>SQLITE_TXN_READ</dt>
+** <dd>The SQLITE_TXN_READ state means that the database is currently
+** in a read transaction. Content has been read from the database file
+** but nothing in the database file has changed. The transaction state
+** will advanced to SQLITE_TXN_WRITE if any changes occur and there are
+** no other conflicting concurrent write transactions. The transaction
+** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
+** [COMMIT].</dd>
+**
+** [[SQLITE_TXN_WRITE]] <dt>SQLITE_TXN_WRITE</dt>
+** <dd>The SQLITE_TXN_WRITE state means that the database is currently
+** in a write transaction. Content has been written to the database file
+** but has not yet committed. The transaction state will change to
+** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
+*/
+#define SQLITE_TXN_NONE 0
+#define SQLITE_TXN_READ 1
+#define SQLITE_TXN_WRITE 2
+
+/*
** CAPI3REF: Find the next prepared statement
** METHOD: sqlite3
**
@@ -7712,7 +7807,10 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_RESULT_INTREAL 27
#define SQLITE_TESTCTRL_PRNG_SEED 28
#define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29
-#define SQLITE_TESTCTRL_LAST 29 /* Largest TESTCTRL */
+#define SQLITE_TESTCTRL_SEEK_COUNT 30
+#define SQLITE_TESTCTRL_TRACEFLAGS 31
+#define SQLITE_TESTCTRL_TUNE 32
+#define SQLITE_TESTCTRL_LAST 32 /* Largest TESTCTRL */
/*
** CAPI3REF: SQL Keyword Checking
@@ -9192,10 +9290,11 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE
**
** If the sqlite3_vtab_nochange(X) routine is called within the [xColumn]
-** method of a [virtual table], then it returns true if and only if the
+** method of a [virtual table], then it might return true if the
** column is being fetched as part of an UPDATE operation during which the
-** column value will not change. Applications might use this to substitute
-** a return value that is less expensive to compute and that the corresponding
+** column value will not change. The virtual table implementation can use
+** this hint as permission to substitute a return value that is less
+** expensive to compute and that the corresponding
** [xUpdate] method understands as a "no-change" value.
**
** If the [xColumn] method calls sqlite3_vtab_nochange() and finds that
@@ -9204,6 +9303,12 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** any of the [sqlite3_result_int|sqlite3_result_xxxxx() interfaces].
** In that case, [sqlite3_value_nochange(X)] will return true for the
** same column in the [xUpdate] method.
+**
+** The sqlite3_vtab_nochange() routine is an optimization. Virtual table
+** implementations should continue to give a correct answer even if the
+** sqlite3_vtab_nochange() interface were to always return false. In the
+** current implementation, the sqlite3_vtab_nochange() interface does always
+** returns false for the enhanced [UPDATE FROM] statement.
*/
SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*);
@@ -9345,6 +9450,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
/*
** CAPI3REF: Flush caches to disk mid-transaction
+** METHOD: sqlite3
**
** ^If a write-transaction is open on [database connection] D when the
** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
@@ -9377,6 +9483,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
/*
** CAPI3REF: The pre-update hook.
+** METHOD: sqlite3
**
** ^These interfaces are only available if SQLite is compiled using the
** [SQLITE_ENABLE_PREUPDATE_HOOK] compile-time option.
@@ -9417,7 +9524,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** seventh parameter is the final rowid value of the row being inserted
** or updated. The value of the seventh parameter passed to the callback
** function is not defined for operations on WITHOUT ROWID tables, or for
-** INSERT operations on rowid tables.
+** DELETE operations on rowid tables.
**
** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()],
** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces
@@ -9455,6 +9562,15 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** triggers; or 2 for changes resulting from triggers called by top-level
** triggers; and so forth.
**
+** When the [sqlite3_blob_write()] API is used to update a blob column,
+** the pre-update hook is invoked with SQLITE_DELETE. This is because the
+** in this case the new values are not available. In this case, when a
+** callback made with op==SQLITE_DELETE is actuall a write using the
+** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
+** the index of the column being written. In other cases, where the
+** pre-update hook is being invoked for some other reason, including a
+** regular DELETE, sqlite3_preupdate_blobwrite() returns -1.
+**
** See also: [sqlite3_update_hook()]
*/
#if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
@@ -9475,10 +9591,12 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
SQLITE_API int sqlite3_preupdate_count(sqlite3 *);
SQLITE_API int sqlite3_preupdate_depth(sqlite3 *);
SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **);
+SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *);
#endif
/*
** CAPI3REF: Low-level system error code
+** METHOD: sqlite3
**
** ^Attempt to return the underlying operating system error code or error
** number that caused the most recent I/O error or failure to open a file.
@@ -9712,8 +9830,8 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory
** allocation error occurs.
**
-** This interface is only available if SQLite is compiled with the
-** [SQLITE_ENABLE_DESERIALIZE] option.
+** This interface is omitted if SQLite is compiled with the
+** [SQLITE_OMIT_DESERIALIZE] option.
*/
SQLITE_API unsigned char *sqlite3_serialize(
sqlite3 *db, /* The database connection */
@@ -9764,8 +9882,8 @@ SQLITE_API unsigned char *sqlite3_serialize(
** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then
** [sqlite3_free()] is invoked on argument P prior to returning.
**
-** This interface is only available if SQLite is compiled with the
-** [SQLITE_ENABLE_DESERIALIZE] option.
+** This interface is omitted if SQLite is compiled with the
+** [SQLITE_OMIT_DESERIALIZE] option.
*/
SQLITE_API int sqlite3_deserialize(
sqlite3 *db, /* The database connection */
@@ -10014,6 +10132,38 @@ SQLITE_API int sqlite3session_create(
*/
SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
+/*
+** CAPIREF: Conigure a Session Object
+** METHOD: sqlite3_session
+**
+** This method is used to configure a session object after it has been
+** created. At present the only valid value for the second parameter is
+** [SQLITE_SESSION_OBJCONFIG_SIZE].
+**
+** Arguments for sqlite3session_object_config()
+**
+** The following values may passed as the the 4th parameter to
+** sqlite3session_object_config().
+**
+** <dt>SQLITE_SESSION_OBJCONFIG_SIZE <dd>
+** This option is used to set, clear or query the flag that enables
+** the [sqlite3session_changeset_size()] API. Because it imposes some
+** computational overhead, this API is disabled by default. Argument
+** pArg must point to a value of type (int). If the value is initially
+** 0, then the sqlite3session_changeset_size() API is disabled. If it
+** is greater than 0, then the same API is enabled. Or, if the initial
+** value is less than zero, no change is made. In all cases the (int)
+** variable is set to 1 if the sqlite3session_changeset_size() API is
+** enabled following the current call, or 0 otherwise.
+**
+** It is an error (SQLITE_MISUSE) to attempt to modify this setting after
+** the first table has been attached to the session object.
+*/
+SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg);
+
+/*
+*/
+#define SQLITE_SESSION_OBJCONFIG_SIZE 1
/*
** CAPI3REF: Enable Or Disable A Session Object
@@ -10259,6 +10409,22 @@ SQLITE_API int sqlite3session_changeset(
);
/*
+** CAPI3REF: Return An Upper-limit For The Size Of The Changeset
+** METHOD: sqlite3_session
+**
+** By default, this function always returns 0. For it to return
+** a useful result, the sqlite3_session object must have been configured
+** to enable this API using sqlite3session_object_config() with the
+** SQLITE_SESSION_OBJCONFIG_SIZE verb.
+**
+** When enabled, this function returns an upper limit, in bytes, for the size
+** of the changeset that might be produced if sqlite3session_changeset() were
+** called. The final changeset size might be equal to or smaller than the
+** size in bytes returned by this function.
+*/
+SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession);
+
+/*
** CAPI3REF: Load The Difference Between Tables Into A Session
** METHOD: sqlite3_session
**
@@ -10376,6 +10542,14 @@ SQLITE_API int sqlite3session_patchset(
SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession);
/*
+** CAPI3REF: Query for the amount of heap memory used by a session object.
+**
+** This API returns the total amount of heap memory in bytes currently
+** used by the session object passed as the only argument.
+*/
+SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession);
+
+/*
** CAPI3REF: Create An Iterator To Traverse A Changeset
** CONSTRUCTOR: sqlite3_changeset_iter
**
@@ -10477,18 +10651,23 @@ SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this
** is not the case, this function returns [SQLITE_MISUSE].
**
-** If argument pzTab is not NULL, then *pzTab is set to point to a
-** nul-terminated utf-8 encoded string containing the name of the table
-** affected by the current change. The buffer remains valid until either
-** sqlite3changeset_next() is called on the iterator or until the
-** conflict-handler function returns. If pnCol is not NULL, then *pnCol is
-** set to the number of columns in the table affected by the change. If
-** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change
+** Arguments pOp, pnCol and pzTab may not be NULL. Upon return, three
+** outputs are set through these pointers:
+**
+** *pOp is set to one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE],
+** depending on the type of change that the iterator currently points to;
+**
+** *pnCol is set to the number of columns in the table affected by the change; and
+**
+** *pzTab is set to point to a nul-terminated utf-8 encoded string containing
+** the name of the table affected by the current change. The buffer remains
+** valid until either sqlite3changeset_next() is called on the iterator
+** or until the conflict-handler function returns.
+**
+** If pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change
** is an indirect change, or false (0) otherwise. See the documentation for
** [sqlite3session_indirect()] for a description of direct and indirect
-** changes. Finally, if pOp is not NULL, then *pOp is set to one of
-** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the
-** type of change that the iterator currently points to.
+** changes.
**
** If no error occurs, SQLITE_OK is returned. If an error does occur, an
** SQLite error code is returned. The values of the output variables may not
diff --git a/dom/html/HTMLObjectElement.cpp b/dom/html/HTMLObjectElement.cpp
index e0e79b69af..a0d06a8c65 100644
--- a/dom/html/HTMLObjectElement.cpp
+++ b/dom/html/HTMLObjectElement.cpp
@@ -193,7 +193,13 @@ HTMLObjectElement::AfterMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName,
// attributes before inserting the node into the document.
if (aNotify && IsInComposedDoc() && mIsDoneAddingChildren &&
aName == nsGkAtoms::data) {
- return LoadObject(aNotify, true);
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ [self = RefPtr<HTMLObjectElement>(this), aNotify]() {
+ if (self->IsInComposedDoc()) {
+ self->LoadObject(aNotify, true);
+ }
+ }));
+ return NS_OK;
}
}
diff --git a/dom/html/HTMLSharedObjectElement.cpp b/dom/html/HTMLSharedObjectElement.cpp
index f0cf4c188c..d7eec215a0 100644
--- a/dom/html/HTMLSharedObjectElement.cpp
+++ b/dom/html/HTMLSharedObjectElement.cpp
@@ -177,8 +177,13 @@ HTMLSharedObjectElement::AfterMaybeChangeAttr(int32_t aNamespaceID,
// a document, just in case that the caller wants to set additional
// attributes before inserting the node into the document.
if (aNotify && IsInComposedDoc() && mIsDoneAddingChildren) {
- nsresult rv = LoadObject(aNotify, true);
- NS_ENSURE_SUCCESS(rv, rv);
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ [self = RefPtr<HTMLSharedObjectElement>(this), aNotify]() {
+ if (self->IsInComposedDoc()) {
+ self->LoadObject(aNotify, true);
+ }
+ }));
+ return NS_OK;
}
}
}
diff --git a/dom/media/CubebUtils.cpp b/dom/media/CubebUtils.cpp
index aa611973db..5ec8363355 100644
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -50,11 +50,11 @@ enum class CubebState {
Shutdown
} sCubebState = CubebState::Uninitialized;
cubeb* sCubebContext;
-double sVolumeScale;
-uint32_t sCubebPlaybackLatencyInMilliseconds;
-uint32_t sCubebMSGLatencyInFrames;
-bool sCubebPlaybackLatencyPrefSet;
-bool sCubebMSGLatencyPrefSet;
+double sVolumeScale = 1.0;
+uint32_t sCubebPlaybackLatencyInMilliseconds = 100;
+uint32_t sCubebMSGLatencyInFrames = 512;
+bool sCubebPlaybackLatencyPrefSet = false;
+bool sCubebMSGLatencyPrefSet = false;
bool sAudioStreamInitEverSucceeded = false;
StaticAutoPtr<char> sBrandName;
@@ -227,7 +227,7 @@ cubeb* GetCubebContextUnlocked()
sBrandName, "Did not initialize sbrandName, and not on the main thread?");
}
- int rv = cubeb_init(&sCubebContext, sBrandName);
+ int rv = cubeb_init(&sCubebContext, sBrandName, nullptr);
NS_WARNING_ASSERTION(rv == CUBEB_OK, "Could not get a cubeb context.");
sCubebState = (rv == CUBEB_OK) ? CubebState::Initialized : CubebState::Uninitialized;
@@ -258,14 +258,23 @@ bool CubebMSGLatencyPrefSet()
return sCubebMSGLatencyPrefSet;
}
-Maybe<uint32_t> GetCubebMSGLatencyInFrames()
+uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params * params)
{
StaticMutexAutoLock lock(sMutex);
- if (!sCubebMSGLatencyPrefSet) {
- return Maybe<uint32_t>();
+ if (sCubebMSGLatencyPrefSet) {
+ MOZ_ASSERT(sCubebMSGLatencyInFrames > 0);
+ return sCubebMSGLatencyInFrames;
}
- MOZ_ASSERT(sCubebMSGLatencyInFrames > 0);
- return Some(sCubebMSGLatencyInFrames);
+ cubeb* context = GetCubebContextUnlocked();
+ if (!context) {
+ return sCubebMSGLatencyInFrames; // default 512
+ }
+ uint32_t latency_frames = 0;
+ if (cubeb_get_min_latency(context, params, &latency_frames) != CUBEB_OK) {
+ NS_WARNING("Could not get minimal latency from cubeb.");
+ return sCubebMSGLatencyInFrames; // default 512
+ }
+ return latency_frames;
}
void InitLibrary()
diff --git a/dom/media/CubebUtils.h b/dom/media/CubebUtils.h
index f434923747..8ebd1af214 100644
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -36,7 +36,7 @@ bool GetFirstStream();
cubeb* GetCubebContext();
cubeb* GetCubebContextUnlocked();
uint32_t GetCubebPlaybackLatencyInMilliseconds();
-Maybe<uint32_t> GetCubebMSGLatencyInFrames();
+uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params * params);
bool CubebLatencyPrefSet();
void GetCurrentBackend(nsAString& aBackend);
diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp
index 90680d8c69..70b59c61f1 100644
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -593,7 +593,6 @@ AudioCallbackDriver::Init()
cubeb_stream_params output;
cubeb_stream_params input;
- uint32_t latency_frames;
MOZ_ASSERT(!NS_IsMainThread(),
"This is blocking and should never run on the main thread.");
@@ -609,14 +608,7 @@ AudioCallbackDriver::Init()
output.format = CUBEB_SAMPLE_FLOAT32NE;
}
- Maybe<uint32_t> latencyPref = CubebUtils::GetCubebMSGLatencyInFrames();
- if (latencyPref) {
- latency_frames = latencyPref.value();
- } else {
- if (cubeb_get_min_latency(cubebContext, output, &latency_frames) != CUBEB_OK) {
- NS_WARNING("Could not get minimal latency from cubeb.");
- }
- }
+ uint32_t latency_frames = CubebUtils::GetCubebMSGLatencyInFrames(&output);
input = output;
input.channels = mInputChannels; // change to support optional stereo capture
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
index f33ded1c5f..5edf7dc4a5 100644
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4339,6 +4339,11 @@ WorkerPrivate::Constructor(JSContext* aCx,
return nullptr;
}
+ // From this point on (worker thread has been started) we
+ // must keep ourself alive. We can now only be cleared by
+ // ClearSelfAndParentEventTargetRef().
+ worker->mSelfRef = worker;
+
worker->EnableDebugger();
RefPtr<CompileScriptRunnable> compiler =
@@ -4348,8 +4353,6 @@ WorkerPrivate::Constructor(JSContext* aCx,
return nullptr;
}
- worker->mSelfRef = worker;
-
return worker.forget();
}
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp
index 9b461e487e..faba010950 100644
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -1529,12 +1529,63 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp)
return true;
}
+static MOZ_MUST_USE bool PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator,
+ HandleObject C, HandleObject promiseObj,
+ HandleObject resolve, HandleObject reject,
+ bool* done);
+
+// ES2020
+static bool
+Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedValue iterable(cx, args.get(0));
+ RootedValue CVal(cx, args.thisv());
+ if (!CVal.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
+ "Receiver of Promise.allSettled call");
+ return false;
+ }
+
+ RootedObject C(cx, &CVal.toObject());
+ RootedObject resultPromise(cx);
+ RootedObject resolve(cx);
+ RootedObject reject(cx);
+ if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false))
+ return false;
+
+ JS::ForOfIterator iter(cx);
+ if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable))
+ return AbruptRejectPromise(cx, args, resultPromise, reject);
+
+ if (!iter.valueIsIterable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
+ "Argument of Promise.allSettled");
+ return AbruptRejectPromise(cx, args, resultPromise, reject);
+ }
+
+ bool done;
+ bool result = PerformPromiseAllSettled(cx, iter, C, resultPromise, resolve, reject, &done);
+
+ if (!result) {
+ if (!done)
+ iter.closeThrow();
+
+ return AbruptRejectPromise(cx, args, resultPromise, reject);
+ }
+
+ args.rval().setObject(*resultPromise);
+ return true;
+}
+
static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle<PromiseObject*> promise,
HandleValue onFulfilled_, HandleValue onRejected_,
HandleObject resultPromise,
HandleObject resolve, HandleObject reject);
static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp);
+static bool PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp);
+static bool PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp);
// Unforgeable version of ES2016, 25.4.4.1.
MOZ_MUST_USE JSObject*
@@ -1848,6 +1899,120 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
}
}
+static MOZ_MUST_USE bool
+PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
+ HandleObject promiseObj, HandleObject resolve, HandleObject reject,
+ bool* done)
+{
+ *done = false;
+
+ RootedObject unwrappedPromiseObj(cx);
+ if (IsWrapper(promiseObj)) {
+ unwrappedPromiseObj = CheckedUnwrap(promiseObj);
+ MOZ_ASSERT(unwrappedPromiseObj);
+ }
+
+ RootedValue CVal(cx, ObjectValue(*C));
+
+ RootedObject valuesArray(cx);
+ if (unwrappedPromiseObj) {
+ JSAutoCompartment ac(cx, unwrappedPromiseObj);
+ valuesArray = NewDenseFullyAllocatedArray(cx, 0);
+ } else {
+ valuesArray = NewDenseFullyAllocatedArray(cx, 0);
+ }
+ if (!valuesArray)
+ return false;
+
+ RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
+ if (!cx->compartment()->wrap(cx, &valuesArrayVal))
+ return false;
+
+ Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj,
+ valuesArrayVal, resolve));
+ if (!dataHolder)
+ return false;
+ RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
+
+ uint32_t index = 0;
+
+ RootedValue nextValue(cx);
+ RootedId indexId(cx);
+
+ while (true) {
+ if (!iterator.next(&nextValue, done)) {
+ *done = true;
+ return false;
+ }
+
+ if (*done) {
+ int32_t remainingCount = dataHolder->decreaseRemainingCount();
+ if (remainingCount == 0) {
+ return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode,
+ promiseObj);
+ }
+ return true;
+ }
+
+ { // Scoped for AutoCompartment
+ JSAutoCompartment ac(cx, valuesArray);
+ indexId = INT_TO_JSID(index);
+ if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue))
+ return false;
+ }
+
+ RootedValue nextPromise(cx);
+ RootedValue staticResolve(cx);
+ RootedValue staticReject(cx);
+
+ // Because Promise.allSettled can continue whether the promise is fulfilled or rejected, we
+ // should only return false if neither condition is true.
+
+ if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve) &&
+ !GetProperty(cx, CVal, cx->names().reject, &staticReject))
+ return false;
+
+ FixedInvokeArgs<1> resolveArgs(cx);
+ resolveArgs[0].set(nextValue);
+ FixedInvokeArgs<1> rejectArgs(cx);
+ rejectArgs[0].set(nextValue);
+ if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise) &&
+ !Call(cx, staticReject, CVal, rejectArgs, &nextPromise))
+ return false;
+
+
+ RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllSettledResolveElementFunction,
+ 1, nullptr,
+ gc::AllocKind::FUNCTION_EXTENDED,
+ GenericObject));
+
+ RootedFunction rejectFunc(cx, NewNativeFunction(cx, PromiseAllSettledRejectElementFunction,
+ 1, nullptr,
+ gc::AllocKind::FUNCTION_EXTENDED,
+ GenericObject));
+ if (!resolveFunc && !rejectFunc) {
+ return false;
+ }
+
+ resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
+ resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
+ Int32Value(index));
+ rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
+ rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
+ Int32Value(index));
+
+ dataHolder->increaseRemainingCount();
+
+ RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
+ RootedValue rejectFunVal(cx, ObjectValue(*rejectFunc));
+ if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal))
+ return false;
+
+ index++;
+ MOZ_ASSERT(index > 0);
+ }
+}
+
// ES2016, 25.4.4.1.2.
static bool
PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp)
@@ -1921,6 +2086,159 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp)
return true;
}
+// ES2020.
+static bool
+PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction resolve(cx, &args.callee().as<JSFunction>());
+ RootedValue xVal(cx, args.get(0));
+ RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data));
+
+ if (dataVal.isUndefined()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ Rooted<PromiseAllDataHolder*> data(cx, &dataVal.toObject().as<PromiseAllDataHolder>());
+
+ resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue());
+
+ int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex)
+ .toInt32();
+
+ RootedValue valuesVal(cx, data->valuesArray());
+ RootedObject valuesObj(cx, &valuesVal.toObject());
+ bool valuesListIsWrapped = false;
+ if (IsWrapper(valuesObj)) {
+ valuesListIsWrapped = true;
+ // See comment for PerformPromiseAll, step 3 for why we unwrap here.
+ valuesObj = UncheckedUnwrap(valuesObj);
+ }
+ NativeObject* values = &valuesObj->as<NativeObject>();
+
+ // The index is guaranteed to be initialized to `undefined`.
+ if (valuesListIsWrapped) {
+ AutoCompartment ac(cx, values);
+ if (!cx->compartment()->wrap(cx, &xVal))
+ return false;
+ }
+
+ RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!obj) {
+ return false;
+ }
+ RootedId id(cx, NameToId(cx->names().status));
+ RootedValue statusValue(cx);
+ statusValue.setString(cx->names().fulfilled);
+ if (!::JS_DefinePropertyById(cx, obj, id, statusValue, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ id = NameToId(cx->names().value);
+ if (!::JS_DefinePropertyById(cx, obj, id, xVal, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ RootedValue objVal(cx, ObjectValue(*obj));
+/* if (needsWrapping) {
+ AutoRealm ar(cx, valuesObj);
+ if (!cx->compartment()->wrap(cx, &objVal)) {
+ return false;
+ }
+ } */
+ values->setDenseElement(index, objVal);
+
+ uint32_t remainingCount = data->decreaseRemainingCount();
+
+ if (remainingCount == 0) {
+ RootedObject resolveAllFun(cx, data->resolveObj());
+ RootedObject promiseObj(cx, data->promiseObj());
+ if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj))
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction resolve(cx, &args.callee().as<JSFunction>());
+ RootedValue xVal(cx, args.get(0));
+ RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data));
+
+ if (dataVal.isUndefined()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ Rooted<PromiseAllDataHolder*> data(cx, &dataVal.toObject().as<PromiseAllDataHolder>());
+
+ resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue());
+
+ int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex)
+ .toInt32();
+
+ RootedValue valuesVal(cx, data->valuesArray());
+ RootedObject valuesObj(cx, &valuesVal.toObject());
+ bool valuesListIsWrapped = false;
+ if (IsWrapper(valuesObj)) {
+ valuesListIsWrapped = true;
+ // See comment for PerformPromiseAll, step 3 for why we unwrap here.
+ valuesObj = UncheckedUnwrap(valuesObj);
+ }
+ NativeObject* values = &valuesObj->as<NativeObject>();
+
+ // The index is guaranteed to be initialized to `undefined`.
+ if (valuesListIsWrapped) {
+ AutoCompartment ac(cx, values);
+ if (!cx->compartment()->wrap(cx, &xVal))
+ return false;
+ }
+
+ RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!obj) {
+ return false;
+ }
+ RootedId id(cx, NameToId(cx->names().status));
+ RootedValue statusValue(cx);
+ statusValue.setString(cx->names().rejected);
+ if (!::JS_DefinePropertyById(cx, obj, id, statusValue, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ RootedValue resultValue(cx);
+ id = NameToId(cx->names().reason);
+ if (!::JS_DefinePropertyById(cx, obj, id, xVal, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ RootedValue objVal(cx, ObjectValue(*obj));
+/* if (needsWrapping) {
+ AutoRealm ar(cx, valuesObj);
+ if (!cx->compartment()->wrap(cx, &objVal)) {
+ return false;
+ }
+ } */
+ values->setDenseElement(index, objVal);
+
+
+ uint32_t remainingCount = data->decreaseRemainingCount();
+
+ if (remainingCount == 0) {
+ RootedObject resolveAllFun(cx, data->resolveObj());
+ RootedObject promiseObj(cx, data->promiseObj());
+ if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj))
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator,
HandleObject C, HandleObject promiseObj,
HandleObject resolve, HandleObject reject,
@@ -3246,6 +3564,7 @@ static const JSPropertySpec promise_properties[] = {
static const JSFunctionSpec promise_static_methods[] = {
JS_FN("all", Promise_static_all, 1, 0),
+ JS_FN("allSettled", Promise_static_allSettled, 1, 0),
JS_FN("race", Promise_static_race, 1, 0),
JS_FN("reject", Promise_reject, 1, 0),
JS_FN("resolve", Promise_static_resolve, 1, 0),
diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h
index 9d95c7f7ab..6d9888469e 100644
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -2121,6 +2121,15 @@ class MacroAssembler : public MacroAssemblerSpecific
inline void assertStackAlignment(uint32_t alignment, int32_t offset = 0);
};
+// StackMacroAssembler checks no GC will happen while it's on the stack.
+class MOZ_RAII StackMacroAssembler : public MacroAssembler {
+ JS::AutoCheckCannotGC nogc;
+
+public:
+ StackMacroAssembler() : MacroAssembler() {}
+ explicit StackMacroAssembler(JSContext* cx) : MacroAssembler(cx) {}
+};
+
static inline Assembler::DoubleCondition
JSOpToDoubleCondition(JSOp op)
{
diff --git a/js/src/moz.build b/js/src/moz.build
index 9cad8e52e1..6664007adb 100644
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -123,7 +123,7 @@ if CONFIG['JS_BUNDLED_EDITLINE']:
DIRS += ['editline']
if CONFIG['JS_NEW_REGEXP']:
- DIRS += ['regexp']
+ DIRS += ['new-regexp']
if not CONFIG['JS_DISABLE_SHELL']:
DIRS += ['shell']
diff --git a/js/src/regexp/RegExpTypes.h b/js/src/new-regexp/RegExpTypes.h
index e260b5bb6d..e260b5bb6d 100644
--- a/js/src/regexp/RegExpTypes.h
+++ b/js/src/new-regexp/RegExpTypes.h
diff --git a/js/src/regexp/VERSION b/js/src/new-regexp/VERSION
index c7d35a2bb8..c7d35a2bb8 100644
--- a/js/src/regexp/VERSION
+++ b/js/src/new-regexp/VERSION
diff --git a/js/src/regexp/gen-regexp-special-case.cc b/js/src/new-regexp/gen-regexp-special-case.cc
index b4a8c3da48..5a82c5d277 100644
--- a/js/src/regexp/gen-regexp-special-case.cc
+++ b/js/src/new-regexp/gen-regexp-special-case.cc
@@ -7,7 +7,7 @@
#include <iostream>
#include <sstream>
-#include "regexp/special-case.h"
+#include "new-regexp/special-case.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/import-irregexp.py b/js/src/new-regexp/import-irregexp.py
index 870387232c..870387232c 100644
--- a/js/src/regexp/import-irregexp.py
+++ b/js/src/new-regexp/import-irregexp.py
diff --git a/js/src/regexp/moz.build b/js/src/new-regexp/moz.build
index 4caa4589c0..2a8fab2ef6 100644
--- a/js/src/regexp/moz.build
+++ b/js/src/new-regexp/moz.build
@@ -34,4 +34,9 @@ if CONFIG['ENABLE_INTL_API']:
SOURCES += [
'property-sequences.cc',
'special-case.cc'
- ] \ No newline at end of file
+ ]
+
+if CONFIG['_MSC_VER']:
+ # This is intended as a temporary workaround to unblock compilation
+ # on VS2015 in warnings as errors mode.
+ CXXFLAGS += ['-wd4275'] \ No newline at end of file
diff --git a/js/src/regexp/property-sequences.cc b/js/src/new-regexp/property-sequences.cc
index e07d6da531..ca1a7f2c3c 100644
--- a/js/src/regexp/property-sequences.cc
+++ b/js/src/new-regexp/property-sequences.cc
@@ -4,7 +4,7 @@
#ifdef V8_INTL_SUPPORT
-#include "regexp/property-sequences.h"
+#include "new-regexp/property-sequences.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/property-sequences.h b/js/src/new-regexp/property-sequences.h
index ed39e23795..f079da7ac6 100644
--- a/js/src/regexp/property-sequences.h
+++ b/js/src/new-regexp/property-sequences.h
@@ -7,7 +7,7 @@
#ifdef V8_INTL_SUPPORT
-#include "regexp/regexp-shim.h"
+#include "new-regexp/regexp-shim.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-ast.cc b/js/src/new-regexp/regexp-ast.cc
index 8f7dd69478..8de26720fa 100644
--- a/js/src/regexp/regexp-ast.cc
+++ b/js/src/new-regexp/regexp-ast.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-ast.h"
+#include "new-regexp/regexp-ast.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-ast.h b/js/src/new-regexp/regexp-ast.h
index 311929d0b9..32bbcf0bf9 100644
--- a/js/src/regexp/regexp-ast.h
+++ b/js/src/new-regexp/regexp-ast.h
@@ -5,7 +5,7 @@
#ifndef V8_REGEXP_REGEXP_AST_H_
#define V8_REGEXP_REGEXP_AST_H_
-#include "regexp/regexp-shim.h"
+#include "new-regexp/regexp-shim.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-bytecode-generator-inl.h b/js/src/new-regexp/regexp-bytecode-generator-inl.h
index 69a054fd20..a2d1ac1cb0 100644
--- a/js/src/regexp/regexp-bytecode-generator-inl.h
+++ b/js/src/new-regexp/regexp-bytecode-generator-inl.h
@@ -5,9 +5,9 @@
#ifndef V8_REGEXP_REGEXP_BYTECODE_GENERATOR_INL_H_
#define V8_REGEXP_REGEXP_BYTECODE_GENERATOR_INL_H_
-#include "regexp/regexp-bytecode-generator.h"
+#include "new-regexp/regexp-bytecode-generator.h"
-#include "regexp/regexp-bytecodes.h"
+#include "new-regexp/regexp-bytecodes.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-bytecode-generator.cc b/js/src/new-regexp/regexp-bytecode-generator.cc
index db151de851..2670322d37 100644
--- a/js/src/regexp/regexp-bytecode-generator.cc
+++ b/js/src/new-regexp/regexp-bytecode-generator.cc
@@ -2,12 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-bytecode-generator.h"
+#include "new-regexp/regexp-bytecode-generator.h"
-#include "regexp/regexp-bytecode-generator-inl.h"
-#include "regexp/regexp-bytecode-peephole.h"
-#include "regexp/regexp-bytecodes.h"
-#include "regexp/regexp-macro-assembler.h"
+#include "new-regexp/regexp-bytecode-generator-inl.h"
+#include "new-regexp/regexp-bytecode-peephole.h"
+#include "new-regexp/regexp-bytecodes.h"
+#include "new-regexp/regexp-macro-assembler.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-bytecode-generator.h b/js/src/new-regexp/regexp-bytecode-generator.h
index f5502464d4..274fd3953d 100644
--- a/js/src/regexp/regexp-bytecode-generator.h
+++ b/js/src/new-regexp/regexp-bytecode-generator.h
@@ -5,7 +5,7 @@
#ifndef V8_REGEXP_REGEXP_BYTECODE_GENERATOR_H_
#define V8_REGEXP_REGEXP_BYTECODE_GENERATOR_H_
-#include "regexp/regexp-macro-assembler.h"
+#include "new-regexp/regexp-macro-assembler.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-bytecode-peephole.cc b/js/src/new-regexp/regexp-bytecode-peephole.cc
index 4266b4a807..f105a50945 100644
--- a/js/src/regexp/regexp-bytecode-peephole.cc
+++ b/js/src/new-regexp/regexp-bytecode-peephole.cc
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-bytecode-peephole.h"
+#include "new-regexp/regexp-bytecode-peephole.h"
-#include "regexp/regexp-bytecodes.h"
+#include "new-regexp/regexp-bytecodes.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-bytecode-peephole.h b/js/src/new-regexp/regexp-bytecode-peephole.h
index 31d5a2d480..781f0c9143 100644
--- a/js/src/regexp/regexp-bytecode-peephole.h
+++ b/js/src/new-regexp/regexp-bytecode-peephole.h
@@ -5,7 +5,7 @@
#ifndef V8_REGEXP_REGEXP_BYTECODE_PEEPHOLE_H_
#define V8_REGEXP_REGEXP_BYTECODE_PEEPHOLE_H_
-#include "regexp/regexp-shim.h"
+#include "new-regexp/regexp-shim.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-bytecodes.cc b/js/src/new-regexp/regexp-bytecodes.cc
index ae8f93ac9e..679a7c06a7 100644
--- a/js/src/regexp/regexp-bytecodes.cc
+++ b/js/src/new-regexp/regexp-bytecodes.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-bytecodes.h"
+#include "new-regexp/regexp-bytecodes.h"
#include <cctype>
diff --git a/js/src/regexp/regexp-bytecodes.h b/js/src/new-regexp/regexp-bytecodes.h
index 1cfef1b2d4..e5ab7cf661 100644
--- a/js/src/regexp/regexp-bytecodes.h
+++ b/js/src/new-regexp/regexp-bytecodes.h
@@ -5,7 +5,7 @@
#ifndef V8_REGEXP_REGEXP_BYTECODES_H_
#define V8_REGEXP_REGEXP_BYTECODES_H_
-#include "regexp/regexp-shim.h"
+#include "new-regexp/regexp-shim.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-compiler-tonode.cc b/js/src/new-regexp/regexp-compiler-tonode.cc
index 257030589d..7de167eefe 100644
--- a/js/src/regexp/regexp-compiler-tonode.cc
+++ b/js/src/new-regexp/regexp-compiler-tonode.cc
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-compiler.h"
+#include "new-regexp/regexp-compiler.h"
-#include "regexp/regexp.h"
+#include "new-regexp/regexp.h"
#ifdef V8_INTL_SUPPORT
-#include "regexp/special-case.h"
+#include "new-regexp/special-case.h"
#endif // V8_INTL_SUPPORT
#ifdef V8_INTL_SUPPORT
diff --git a/js/src/regexp/regexp-compiler.cc b/js/src/new-regexp/regexp-compiler.cc
index c0070061f8..98771354cf 100644
--- a/js/src/regexp/regexp-compiler.cc
+++ b/js/src/new-regexp/regexp-compiler.cc
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-compiler.h"
+#include "new-regexp/regexp-compiler.h"
-#include "regexp/regexp-macro-assembler-arch.h"
+#include "new-regexp/regexp-macro-assembler-arch.h"
#ifdef V8_INTL_SUPPORT
-#include "regexp/special-case.h"
+#include "new-regexp/special-case.h"
#endif // V8_INTL_SUPPORT
#ifdef V8_INTL_SUPPORT
diff --git a/js/src/regexp/regexp-compiler.h b/js/src/new-regexp/regexp-compiler.h
index 1954f1a4c4..186d5e838c 100644
--- a/js/src/regexp/regexp-compiler.h
+++ b/js/src/new-regexp/regexp-compiler.h
@@ -7,7 +7,7 @@
#include <bitset>
-#include "regexp/regexp-nodes.h"
+#include "new-regexp/regexp-nodes.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-dotprinter.cc b/js/src/new-regexp/regexp-dotprinter.cc
index 9bf800dfc2..2bf393c32b 100644
--- a/js/src/regexp/regexp-dotprinter.cc
+++ b/js/src/new-regexp/regexp-dotprinter.cc
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-dotprinter.h"
+#include "new-regexp/regexp-dotprinter.h"
-#include "regexp/regexp-compiler.h"
+#include "new-regexp/regexp-compiler.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-dotprinter.h b/js/src/new-regexp/regexp-dotprinter.h
index e5781184c0..0bd03e77f4 100644
--- a/js/src/regexp/regexp-dotprinter.h
+++ b/js/src/new-regexp/regexp-dotprinter.h
@@ -5,7 +5,7 @@
#ifndef V8_REGEXP_REGEXP_DOTPRINTER_H_
#define V8_REGEXP_REGEXP_DOTPRINTER_H_
-#include "regexp/regexp-shim.h"
+#include "new-regexp/regexp-shim.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-error.cc b/js/src/new-regexp/regexp-error.cc
index 3906f9d9ff..9db98d4b83 100644
--- a/js/src/regexp/regexp-error.cc
+++ b/js/src/new-regexp/regexp-error.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-error.h"
+#include "new-regexp/regexp-error.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-error.h b/js/src/new-regexp/regexp-error.h
index 4b495f07d1..4b495f07d1 100644
--- a/js/src/regexp/regexp-error.h
+++ b/js/src/new-regexp/regexp-error.h
diff --git a/js/src/regexp/regexp-interpreter.cc b/js/src/new-regexp/regexp-interpreter.cc
index 7735d68855..7a492fca2a 100644
--- a/js/src/regexp/regexp-interpreter.cc
+++ b/js/src/new-regexp/regexp-interpreter.cc
@@ -4,12 +4,12 @@
// A simple interpreter for the Irregexp byte code.
-#include "regexp/regexp-interpreter.h"
+#include "new-regexp/regexp-interpreter.h"
-#include "regexp/regexp-bytecodes.h"
-#include "regexp/regexp-macro-assembler.h"
-#include "regexp/regexp-stack.h" // For kMaximumStackSize.
-#include "regexp/regexp.h"
+#include "new-regexp/regexp-bytecodes.h"
+#include "new-regexp/regexp-macro-assembler.h"
+#include "new-regexp/regexp-stack.h" // For kMaximumStackSize.
+#include "new-regexp/regexp.h"
#ifdef V8_INTL_SUPPORT
#include "unicode/uchar.h"
@@ -22,6 +22,7 @@
#define V8_USE_COMPUTED_GOTO 1
#endif // V8_HAS_COMPUTED_GOTO
+
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-interpreter.h b/js/src/new-regexp/regexp-interpreter.h
index c3f6c119e8..b4c0da2b7b 100644
--- a/js/src/regexp/regexp-interpreter.h
+++ b/js/src/new-regexp/regexp-interpreter.h
@@ -7,7 +7,7 @@
#ifndef V8_REGEXP_REGEXP_INTERPRETER_H_
#define V8_REGEXP_REGEXP_INTERPRETER_H_
-#include "regexp/regexp.h"
+#include "new-regexp/regexp.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-macro-assembler-arch.h b/js/src/new-regexp/regexp-macro-assembler-arch.h
index 1baa5ddd52..8aeb8c433f 100644
--- a/js/src/regexp/regexp-macro-assembler-arch.h
+++ b/js/src/new-regexp/regexp-macro-assembler-arch.h
@@ -16,7 +16,7 @@
#define RegexpMacroAssemblerArch_h
#include "jit/MacroAssembler.h"
-#include "regexp/regexp-macro-assembler.h"
+#include "new-regexp/regexp-macro-assembler.h"
namespace v8 {
namespace internal {
@@ -186,7 +186,7 @@ class SMRegExpMacroAssembler final : public NativeRegExpMacroAssembler {
if (num_registers_ <= register_index) {
num_registers_ = register_index + 1;
}
- static_assert(alignof(uintptr_t) <= alignof(FrameData));
+ static_assert(alignof(uintptr_t) <= alignof(FrameData),"Regexp: Alignment of uintptr_t and FrameData mismatch");
return sizeof(FrameData) + register_index * sizeof(uintptr_t*);
}
diff --git a/js/src/regexp/regexp-macro-assembler-tracer.cc b/js/src/new-regexp/regexp-macro-assembler-tracer.cc
index b71a0f48e9..8eb587c3c8 100644
--- a/js/src/regexp/regexp-macro-assembler-tracer.cc
+++ b/js/src/new-regexp/regexp-macro-assembler-tracer.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-macro-assembler-tracer.h"
+#include "new-regexp/regexp-macro-assembler-tracer.h"
namespace v8 {
diff --git a/js/src/regexp/regexp-macro-assembler-tracer.h b/js/src/new-regexp/regexp-macro-assembler-tracer.h
index 5332e59b89..0596a18ba1 100644
--- a/js/src/regexp/regexp-macro-assembler-tracer.h
+++ b/js/src/new-regexp/regexp-macro-assembler-tracer.h
@@ -5,7 +5,7 @@
#ifndef V8_REGEXP_REGEXP_MACRO_ASSEMBLER_TRACER_H_
#define V8_REGEXP_REGEXP_MACRO_ASSEMBLER_TRACER_H_
-#include "regexp/regexp-macro-assembler.h"
+#include "new-regexp/regexp-macro-assembler.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-macro-assembler.cc b/js/src/new-regexp/regexp-macro-assembler.cc
index 7f8de25437..52c1cb1ba3 100644
--- a/js/src/regexp/regexp-macro-assembler.cc
+++ b/js/src/new-regexp/regexp-macro-assembler.cc
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-macro-assembler.h"
+#include "new-regexp/regexp-macro-assembler.h"
-#include "regexp/regexp-stack.h"
+#include "new-regexp/regexp-stack.h"
#ifdef V8_INTL_SUPPORT
#include "unicode/uchar.h"
diff --git a/js/src/regexp/regexp-macro-assembler.h b/js/src/new-regexp/regexp-macro-assembler.h
index ef3961a70a..60d712dfc3 100644
--- a/js/src/regexp/regexp-macro-assembler.h
+++ b/js/src/new-regexp/regexp-macro-assembler.h
@@ -5,9 +5,9 @@
#ifndef V8_REGEXP_REGEXP_MACRO_ASSEMBLER_H_
#define V8_REGEXP_REGEXP_MACRO_ASSEMBLER_H_
-#include "regexp/regexp-ast.h"
-#include "regexp/regexp-shim.h"
-#include "regexp/regexp.h"
+#include "new-regexp/regexp-ast.h"
+#include "new-regexp/regexp-shim.h"
+#include "new-regexp/regexp.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-native-macro-assembler.cc b/js/src/new-regexp/regexp-native-macro-assembler.cc
index 15182ad713..01453a9374 100644
--- a/js/src/regexp/regexp-native-macro-assembler.cc
+++ b/js/src/new-regexp/regexp-native-macro-assembler.cc
@@ -9,12 +9,17 @@
// found in the LICENSE file.
#include "jit/Linker.h"
-#include "regexp/regexp-macro-assembler-arch.h"
-#include "regexp/regexp-stack.h"
+#include "gc/Zone.h"
+#include "new-regexp/regexp-macro-assembler-arch.h"
+#include "new-regexp/regexp-stack.h"
#include "vm/MatchPairs.h"
#include "jit/MacroAssembler-inl.h"
+using namespace js;
+using namespace js::irregexp;
+using namespace js::jit;
+
namespace v8 {
namespace internal {
@@ -84,19 +89,9 @@ void SMRegExpMacroAssembler::AdvanceRegister(int reg, int by) {
}
void SMRegExpMacroAssembler::Backtrack() {
- // Check for an interrupt. We have to restart from the beginning if we
- // are interrupted, so we only check for urgent interrupts.
- js::jit::Label noInterrupt;
- masm_.branchTest32(
- Assembler::Zero, AbsoluteAddress(cx_->addressOfInterruptBits()),
- Imm32(uint32_t(js::InterruptReason::CallbackUrgent)), &noInterrupt);
- masm_.movePtr(ImmWord(js::RegExpRunStatus_Error), temp0_);
- masm_.jump(&exit_label_);
- masm_.bind(&noInterrupt);
-
- // Pop code location from backtrack stack and jump to location.
- Pop(temp0_);
- masm_.jump(temp0_);
+ // Pop code location from backtrack stack and jump to location.
+ Pop(temp0_);
+ masm_.jump(temp0_);
}
void SMRegExpMacroAssembler::Bind(Label* label) {
@@ -546,7 +541,8 @@ bool SMRegExpMacroAssembler::CheckSpecialCharacterClass(uc16 type,
masm_.branch32(Assembler::Above, current_character_, Imm32('z'),
no_match);
}
- static_assert(arraysize(word_character_map) > unibrow::Latin1::kMaxChar);
+ static_assert(arraysize(word_character_map) > unibrow::Latin1::kMaxChar,
+ "regex: arraysize(word_character_map) > unibrow::Latin1::kMaxChar");
masm_.movePtr(ImmPtr(word_character_map), temp0_);
masm_.load8ZeroExtend(
BaseIndex(temp0_, current_character_, js::jit::TimesOne), temp0_);
@@ -558,7 +554,8 @@ bool SMRegExpMacroAssembler::CheckSpecialCharacterClass(uc16 type,
if (mode_ != LATIN1) {
masm_.branch32(Assembler::Above, current_character_, Imm32('z'), &done);
}
- static_assert(arraysize(word_character_map) > unibrow::Latin1::kMaxChar);
+ static_assert(arraysize(word_character_map) > unibrow::Latin1::kMaxChar,
+ "regex: arraysize(word_character_map) > unibrow::Latin1::kMaxChar");
masm_.movePtr(ImmPtr(word_character_map), temp0_);
masm_.load8ZeroExtend(
BaseIndex(temp0_, current_character_, js::jit::TimesOne), temp0_);
@@ -824,7 +821,7 @@ static Handle<HeapObject> DummyCode() {
// Finalize code. This is called last, so that we know how many
// registers we need.
Handle<HeapObject> SMRegExpMacroAssembler::GetCode(Handle<String> source) {
- if (!cx_->realm()->ensureJitRealmExists(cx_)) {
+ if (!cx_->compartment()->ensureJitCompartmentExists(cx_)) {
return DummyCode();
}
@@ -841,8 +838,9 @@ Handle<HeapObject> SMRegExpMacroAssembler::GetCode(Handle<String> source) {
stackOverflowHandler();
Linker linker(masm_);
- JitCode* code = linker.newCode(cx_, js::jit::CodeKind::RegExp);
+ JitCode* code = linker.newCode<NoGC>(cx_, REGEXP_CODE);
if (!code) {
+ ReportOutOfMemory(cx_);
return DummyCode();
}
@@ -1161,7 +1159,7 @@ SMRegExpMacroAssembler::Implementation() {
/*static */
uint32_t SMRegExpMacroAssembler::CaseInsensitiveCompareStrings(
const char16_t* substring1, const char16_t* substring2, size_t byteLength) {
- js::AutoUnsafeCallWithABI unsafe;
+ JS::AutoCheckCannotGC nogc;
MOZ_ASSERT(byteLength % sizeof(char16_t) == 0);
size_t length = byteLength / sizeof(char16_t);
@@ -1184,7 +1182,7 @@ uint32_t SMRegExpMacroAssembler::CaseInsensitiveCompareStrings(
/*static */
uint32_t SMRegExpMacroAssembler::CaseInsensitiveCompareUCStrings(
const char16_t* substring1, const char16_t* substring2, size_t byteLength) {
- js::AutoUnsafeCallWithABI unsafe;
+ JS::AutoCheckCannotGC nogc;
MOZ_ASSERT(byteLength % sizeof(char16_t) == 0);
size_t length = byteLength / sizeof(char16_t);
@@ -1206,7 +1204,7 @@ uint32_t SMRegExpMacroAssembler::CaseInsensitiveCompareUCStrings(
/* static */
bool SMRegExpMacroAssembler::GrowBacktrackStack(RegExpStack* regexp_stack) {
- js::AutoUnsafeCallWithABI unsafe;
+ JS::AutoCheckCannotGC nogc;
size_t size = regexp_stack->stack_capacity();
return !!regexp_stack->EnsureCapacity(size * 2);
}
diff --git a/js/src/regexp/regexp-nodes.h b/js/src/new-regexp/regexp-nodes.h
index 50c843c20c..099687c25e 100644
--- a/js/src/regexp/regexp-nodes.h
+++ b/js/src/new-regexp/regexp-nodes.h
@@ -5,7 +5,7 @@
#ifndef V8_REGEXP_REGEXP_NODES_H_
#define V8_REGEXP_REGEXP_NODES_H_
-#include "regexp/regexp-macro-assembler.h"
+#include "new-regexp/regexp-macro-assembler.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp-parser.cc b/js/src/new-regexp/regexp-parser.cc
index e2bbb6ed03..a26e354389 100644
--- a/js/src/regexp/regexp-parser.cc
+++ b/js/src/new-regexp/regexp-parser.cc
@@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-parser.h"
+#include "new-regexp/regexp-parser.h"
#include <vector>
-#include "regexp/property-sequences.h"
-#include "regexp/regexp-macro-assembler.h"
-#include "regexp/regexp.h"
+#include "new-regexp/property-sequences.h"
+#include "new-regexp/regexp-macro-assembler.h"
+#include "new-regexp/regexp.h"
#ifdef V8_INTL_SUPPORT
#include "unicode/uniset.h"
diff --git a/js/src/regexp/regexp-parser.h b/js/src/new-regexp/regexp-parser.h
index 131d12161f..1b2a9fe18c 100644
--- a/js/src/regexp/regexp-parser.h
+++ b/js/src/new-regexp/regexp-parser.h
@@ -5,8 +5,8 @@
#ifndef V8_REGEXP_REGEXP_PARSER_H_
#define V8_REGEXP_REGEXP_PARSER_H_
-#include "regexp/regexp-ast.h"
-#include "regexp/regexp-error.h"
+#include "new-regexp/regexp-ast.h"
+#include "new-regexp/regexp-error.h"
namespace v8 {
namespace internal {
@@ -327,7 +327,9 @@ class V8_EXPORT_PRIVATE RegExpParser {
bool operator()(const RegExpCapture* lhs, const RegExpCapture* rhs) const {
DCHECK_NOT_NULL(lhs);
DCHECK_NOT_NULL(rhs);
- return *lhs->name() < *rhs->name();
+ ZoneVector<uc16> lhname = *lhs->name();
+ ZoneVector<uc16> rhname = *rhs->name();
+ return lhname < rhname;
}
};
diff --git a/js/src/regexp/regexp-shim.cc b/js/src/new-regexp/regexp-shim.cc
index 3f3fa40eb0..51a9e2d83b 100644
--- a/js/src/regexp/regexp-shim.cc
+++ b/js/src/new-regexp/regexp-shim.cc
@@ -10,8 +10,10 @@
#include <iostream>
-#include "regexp/regexp-shim.h"
-#include "regexp/regexp-stack.h"
+#include "new-regexp/regexp-shim.h"
+#include "new-regexp/regexp-stack.h"
+
+#include "mozilla/Sprintf.h" // for SprintfLiteral
namespace v8 {
namespace internal {
@@ -125,8 +127,6 @@ PseudoHandle<ByteArrayData> ByteArray::takeOwnership(Isolate* isolate) {
}
void Isolate::trace(JSTracer* trc) {
- js::gc::AssertRootMarkingPhase(trc);
-
for (auto iter = handleArena_.Iter(); !iter.Done(); iter.Next()) {
auto& elem = iter.Get();
JS::GCPolicy<JS::Value>::trace(trc, &elem, "Isolate handle arena");
diff --git a/js/src/regexp/regexp-shim.h b/js/src/new-regexp/regexp-shim.h
index 7677da084b..c49c25ff13 100644
--- a/js/src/regexp/regexp-shim.h
+++ b/js/src/new-regexp/regexp-shim.h
@@ -20,14 +20,15 @@
#include <algorithm>
#include <cctype>
+#include <iostream> // needed for gcc 10
#include "jit/Label.h"
#include "jit/shared/Assembler-shared.h"
#include "js/Value.h"
-#include "regexp/RegExpTypes.h"
-#include "regexp/util/flags.h"
-#include "regexp/util/vector.h"
-#include "regexp/util/zone.h"
+#include "new-regexp/RegExpTypes.h"
+#include "new-regexp/util/flags.h"
+#include "new-regexp/util/vector.h"
+#include "new-regexp/util/zone.h"
#include "vm/NativeObject.h"
// Forward declaration of classes
@@ -1172,7 +1173,6 @@ extern bool FLAG_trace_regexp_bytecodes;
extern bool FLAG_trace_regexp_parser;
extern bool FLAG_trace_regexp_peephole_optimization;
-#define V8_USE_COMPUTED_GOTO 1
#define COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER
} // namespace internal
diff --git a/js/src/regexp/regexp-stack.cc b/js/src/new-regexp/regexp-stack.cc
index b8819e48b6..c8944541c7 100644
--- a/js/src/regexp/regexp-stack.cc
+++ b/js/src/new-regexp/regexp-stack.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "regexp/regexp-stack.h"
+#include "new-regexp/regexp-stack.h"
namespace v8 {
diff --git a/js/src/regexp/regexp-stack.h b/js/src/new-regexp/regexp-stack.h
index 0b452c0059..e32d0ed1f5 100644
--- a/js/src/regexp/regexp-stack.h
+++ b/js/src/new-regexp/regexp-stack.h
@@ -5,7 +5,7 @@
#ifndef V8_REGEXP_REGEXP_STACK_H_
#define V8_REGEXP_REGEXP_STACK_H_
-#include "regexp/regexp-shim.h"
+#include "new-regexp/regexp-shim.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/regexp.h b/js/src/new-regexp/regexp.h
index a36662b78a..f1e403bf03 100644
--- a/js/src/regexp/regexp.h
+++ b/js/src/new-regexp/regexp.h
@@ -5,8 +5,8 @@
#ifndef V8_REGEXP_REGEXP_H_
#define V8_REGEXP_REGEXP_H_
-#include "regexp/regexp-error.h"
-#include "regexp/regexp-shim.h"
+#include "new-regexp/regexp-error.h"
+#include "new-regexp/regexp-shim.h"
namespace v8 {
namespace internal {
diff --git a/js/src/regexp/special-case.cc b/js/src/new-regexp/special-case.cc
index 6b12d28d7d..d767b94c20 100644
--- a/js/src/regexp/special-case.cc
+++ b/js/src/new-regexp/special-case.cc
@@ -11,7 +11,7 @@
// Semantics: Canonicalize) step 3.
#ifdef V8_INTL_SUPPORT
-#include "regexp/special-case.h"
+#include "new-regexp/special-case.h"
#include "unicode/uniset.h"
namespace v8 {
diff --git a/js/src/regexp/special-case.h b/js/src/new-regexp/special-case.h
index 3aca983028..31dfd78582 100644
--- a/js/src/regexp/special-case.h
+++ b/js/src/new-regexp/special-case.h
@@ -6,7 +6,7 @@
#define V8_REGEXP_SPECIAL_CASE_H_
#ifdef V8_INTL_SUPPORT
-#include "regexp/regexp-shim.h"
+#include "new-regexp/regexp-shim.h"
#include "unicode/uchar.h"
#include "unicode/uniset.h"
diff --git a/js/src/regexp/util/flags.h b/js/src/new-regexp/util/flags.h
index 1fa421fc0c..1fa421fc0c 100644
--- a/js/src/regexp/util/flags.h
+++ b/js/src/new-regexp/util/flags.h
diff --git a/js/src/regexp/util/unicode.cc b/js/src/new-regexp/util/unicode.cc
index da8cef4445..ba9ea607cc 100644
--- a/js/src/regexp/util/unicode.cc
+++ b/js/src/new-regexp/util/unicode.cc
@@ -5,7 +5,7 @@
// This file is a subset of:
// https://github.com/v8/v8/blob/master/src/strings/unicode.cc
-#include "regexp/regexp-shim.h"
+#include "new-regexp/regexp-shim.h"
#ifdef V8_INTL_SUPPORT
#include "unicode/uchar.h"
diff --git a/js/src/regexp/util/vector.h b/js/src/new-regexp/util/vector.h
index 2419447d67..435318ce71 100644
--- a/js/src/regexp/util/vector.h
+++ b/js/src/new-regexp/util/vector.h
@@ -45,9 +45,9 @@ void DeleteArray(T* array) {
template <typename T>
class Vector {
public:
- constexpr Vector() : start_(nullptr), length_(0) {}
+ Vector() : start_(nullptr), length_(0) {}
- constexpr Vector(T* data, size_t length) : start_(data), length_(length) {
+ Vector(T* data, size_t length) : start_(data), length_(length) {
MOZ_ASSERT_IF(length != 0, data != nullptr);
}
diff --git a/js/src/regexp/util/zone.h b/js/src/new-regexp/util/zone.h
index 5a963dd562..7183f77b70 100644
--- a/js/src/regexp/util/zone.h
+++ b/js/src/new-regexp/util/zone.h
@@ -13,7 +13,7 @@
#include "ds/LifoAlloc.h"
#include "ds/Sort.h"
-#include "regexp/util/vector.h"
+#include "new-regexp/util/vector.h"
namespace v8 {
namespace internal {
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index 7df7563b5b..445b8a6bb3 100644
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -325,6 +325,7 @@
macro(startTimestamp, startTimestamp, "startTimestamp") \
macro(state, state, "state") \
macro(static, static_, "static") \
+ macro(status, status, "status") \
macro(std_Function_apply, std_Function_apply, "std_Function_apply") \
macro(sticky, sticky, "sticky") \
macro(StringIterator, StringIterator, "String Iterator") \
diff --git a/layout/media/symbols.def.in b/layout/media/symbols.def.in
index 6e2364f51a..0880bedc7d 100644
--- a/layout/media/symbols.def.in
+++ b/layout/media/symbols.def.in
@@ -155,7 +155,6 @@ cubeb_stream_start
cubeb_stream_stop
cubeb_stream_get_latency
cubeb_stream_set_volume
-cubeb_stream_set_panning
cubeb_stream_get_current_device
cubeb_stream_device_destroy
cubeb_stream_register_device_changed_callback
diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp
index 74ba732885..9d0821406b 100644
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -4640,8 +4640,8 @@ struct LengthPercentNumberCalcOps : public css::NumbersAlreadyNormalizedOps
MergeMultiplicativeR(nsCSSUnit aCalcFunction,
result_type aValue1, float aValue2)
{
- MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_TimesR ||
- aCalcFunction == eCSSUnit_Divided,
+ MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_R ||
+ aCalcFunction == eCSSUnit_Calc_Divided,
"unexpected unit");
result_type result;
if (aCalcFunction == eCSSUnit_Calc_Divided) {
diff --git a/media/libcubeb/AUTHORS b/media/libcubeb/AUTHORS
index 0fde65baad..f0f9595227 100644
--- a/media/libcubeb/AUTHORS
+++ b/media/libcubeb/AUTHORS
@@ -13,3 +13,4 @@ Landry Breuil <landry@openbsd.org>
Jacek Caban <jacek@codeweavers.com>
Paul Hancock <Paul.Hancock.17041993@live.com>
Ted Mielczarek <ted@mielczarek.org>
+Chun-Min Chang <chun.m.chang@gmail.com>
diff --git a/media/libcubeb/README.md b/media/libcubeb/README.md
index d26b3b645b..92df4f22c2 100644
--- a/media/libcubeb/README.md
+++ b/media/libcubeb/README.md
@@ -1,6 +1,7 @@
-[![Build Status](https://travis-ci.org/kinetiknz/cubeb.svg?branch=master)](https://travis-ci.org/kinetiknz/cubeb)
-[![Build status](https://ci.appveyor.com/api/projects/status/osv2r0m1j1nt9csr/branch/master?svg=true)](https://ci.appveyor.com/project/kinetiknz/cubeb/branch/master)
+[![Build Status](https://github.com/mozilla/cubeb/actions/workflows/build.yml/badge.svg)](https://github.com/mozilla/cubeb/actions/workflows/build.yml)
See INSTALL.md for build instructions.
+See [Backend Support](https://github.com/kinetiknz/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend.
+
Licensed under an ISC-style license. See LICENSE for details.
diff --git a/media/libcubeb/README_MCP b/media/libcubeb/README_MCP
new file mode 100644
index 0000000000..8fdebb0457
--- /dev/null
+++ b/media/libcubeb/README_MCP
@@ -0,0 +1,8 @@
+The source from this directory was copied from the cubeb
+git repository using the update.sh script. The only changes
+made were those applied by update.sh and the addition of
+moz.build build files for the Mozilla build system.
+
+The cubeb git repository is: https://github.com/mozilla/cubeb.git
+
+The git commit ID used was 6ce95962c8bb1442a725cbacdc77d5d8cbce43ad.
diff --git a/media/libcubeb/README_MOZILLA b/media/libcubeb/README_MOZILLA
deleted file mode 100644
index c7f5e9c638..0000000000
--- a/media/libcubeb/README_MOZILLA
+++ /dev/null
@@ -1,8 +0,0 @@
-The source from this directory was copied from the cubeb
-git repository using the update.sh script. The only changes
-made were those applied by update.sh and the addition of
-Makefile.in build files for the Mozilla build system.
-
-The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
-
-The git commit ID used was f8467510a8b36793b1b8b7e85461e2e189eb7015.
diff --git a/media/libcubeb/bug1292803_pulse_assert.patch b/media/libcubeb/bug1292803_pulse_assert.patch
deleted file mode 100644
index 8dee88777b..0000000000
--- a/media/libcubeb/bug1292803_pulse_assert.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-commit 2c7617f5ca20b764c605e19af490889c761e65e2
-Author: Matthew Gregan <kinetik@flim.org>
-Date: Thu Nov 10 19:07:07 2016 +1300
-
- pulse: Bail early from pulse_defer_event_cb when shutting down.
-
- When starting a stream, trigger_user_callback may be called from
- stream_write_callback and immediately enter a drain situation, creating
- a drain timer and setting shutdown to true. If pulse_defer_event_cb
- then runs without checking for shutdown, it can overwrite the current
- timer with a new timer, resulting in a leaked timer and a null pointer
- assertion.
-
-diff --git a/src/cubeb_pulse.c b/src/cubeb_pulse.c
-index 5b61bda..86f2ba3 100644
---- a/src/cubeb_pulse.c
-+++ b/src/cubeb_pulse.c
-@@ -181,9 +181,9 @@ static void
- stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u)
- {
- (void)a;
-- (void)e;
- (void)tv;
- cubeb_stream * stm = u;
-+ assert(stm->drain_timer == e);
- stream_state_change_callback(stm, CUBEB_STATE_DRAINED);
- /* there's no pa_rttime_free, so use this instead. */
- a->time_free(stm->drain_timer);
-@@ -267,6 +267,7 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cub
- assert(r == 0 || r == -PA_ERR_NODATA);
- /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
- /* arbitrary safety margin: double the current latency. */
-+ assert(!stm->drain_timer);
- stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm);
- stm->shutdown = 1;
- return;
-@@ -851,6 +852,9 @@ pulse_defer_event_cb(pa_mainloop_api * a, void * userdata)
- {
- (void)a;
- cubeb_stream * stm = userdata;
-+ if (stm->shutdown) {
-+ return;
-+ }
- size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream);
- trigger_user_callback(stm->output_stream, NULL, writable_size, stm);
- }
diff --git a/media/libcubeb/bug1302231_emergency_bailout.patch b/media/libcubeb/bug1302231_emergency_bailout.patch
deleted file mode 100644
index 82152c23f6..0000000000
--- a/media/libcubeb/bug1302231_emergency_bailout.patch
+++ /dev/null
@@ -1,140 +0,0 @@
-From 37ce70d4400a2ab6b59ee432b41d4ffcc9d136ff Mon Sep 17 00:00:00 2001
-From: Paul Adenot <paul@paul.cx>
-Date: Thu, 10 Nov 2016 21:45:14 +0100
-Subject: [PATCH] Bail out safely from the rendering loop if we could not join
- the rendering thread in time (#187)
-
-Bail out safely from the rendering loop if we could not join the rendering thread in time.
----
- src/cubeb_wasapi.cpp | 41 ++++++++++++++++++++++++++++++++++++-----
- 1 file changed, 36 insertions(+), 5 deletions(-)
-
-diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp
-index 9e689b9..519d5ca 100644
---- a/src/cubeb_wasapi.cpp
-+++ b/src/cubeb_wasapi.cpp
-@@ -22,6 +22,7 @@
- #include <algorithm>
- #include <memory>
- #include <limits>
-+#include <atomic>
-
- #include "cubeb/cubeb.h"
- #include "cubeb-internal.h"
-@@ -220,9 +221,11 @@ struct cubeb_stream
- float volume;
- /* True if the stream is draining. */
- bool draining;
-+ /* True when we've destroyed the stream. This pointer is leaked on stream
-+ * destruction if we could not join the thread. */
-+ std::atomic<std::atomic<bool>*> emergency_bailout;
- };
-
--
- class wasapi_endpoint_notification_client : public IMMNotificationClient
- {
- public:
-@@ -781,6 +784,7 @@ static unsigned int __stdcall
- wasapi_stream_render_loop(LPVOID stream)
- {
- cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
-+ std::atomic<bool> * emergency_bailout = stm->emergency_bailout;
-
- bool is_playing = true;
- HANDLE wait_array[4] = {
-@@ -820,6 +824,10 @@ wasapi_stream_render_loop(LPVOID stream)
- wait_array,
- FALSE,
- 1000);
-+ if (*emergency_bailout) {
-+ delete emergency_bailout;
-+ return 0;
-+ }
- if (waitResult != WAIT_TIMEOUT) {
- timeout_count = 0;
- }
-@@ -1134,12 +1142,13 @@ int wasapi_init(cubeb ** context, char const * context_name)
- }
-
- namespace {
--void stop_and_join_render_thread(cubeb_stream * stm)
-+bool stop_and_join_render_thread(cubeb_stream * stm)
- {
-+ bool rv = true;
- LOG("Stop and join render thread.");
- if (!stm->thread) {
- LOG("No thread present.");
-- return;
-+ return true;
- }
-
- BOOL ok = SetEvent(stm->shutdown_event);
-@@ -1153,11 +1162,15 @@ void stop_and_join_render_thread(cubeb_stream * stm)
- if (r == WAIT_TIMEOUT) {
- /* Something weird happened, leak the thread and continue the shutdown
- * process. */
-+ *(stm->emergency_bailout) = true;
- LOG("Destroy WaitForSingleObject on thread timed out,"
- " leaking the thread: %d", GetLastError());
-+ rv = false;
- }
- if (r == WAIT_FAILED) {
-+ *(stm->emergency_bailout) = true;
- LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError());
-+ rv = false;
- }
-
- LOG("Closing thread.");
-@@ -1167,6 +1180,8 @@ void stop_and_join_render_thread(cubeb_stream * stm)
-
- CloseHandle(stm->shutdown_event);
- stm->shutdown_event = 0;
-+
-+ return rv;
- }
-
- void wasapi_destroy(cubeb * context)
-@@ -1775,7 +1790,16 @@ void wasapi_stream_destroy(cubeb_stream * stm)
- {
- XASSERT(stm);
-
-- stop_and_join_render_thread(stm);
-+ // Only free stm->emergency_bailout if we could not join the thread.
-+ // If we could not join the thread, stm->emergency_bailout is true
-+ // and is still alive until the thread wakes up and exits cleanly.
-+ if (stop_and_join_render_thread(stm)) {
-+ delete stm->emergency_bailout.load();
-+ stm->emergency_bailout = nullptr;
-+ } else {
-+ // If we're leaking, it must be that this is true.
-+ assert(*(stm->emergency_bailout));
-+ }
-
- unregister_notification_client(stm);
-
-@@ -1844,6 +1868,8 @@ int wasapi_stream_start(cubeb_stream * stm)
-
- auto_lock lock(stm->stream_reset_lock);
-
-+ stm->emergency_bailout = new std::atomic<bool>(false);
-+
- if (stm->output_client) {
- int rv = stream_start_one_side(stm, OUTPUT);
- if (rv != CUBEB_OK) {
-@@ -1903,7 +1929,12 @@ int wasapi_stream_stop(cubeb_stream * stm)
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
- }
-
-- stop_and_join_render_thread(stm);
-+ if (stop_and_join_render_thread(stm)) {
-+ if (stm->emergency_bailout.load()) {
-+ delete stm->emergency_bailout.load();
-+ stm->emergency_bailout = nullptr;
-+ }
-+ }
-
- return CUBEB_OK;
- }
---
-2.7.4
-
diff --git a/media/libcubeb/fix-crashes.patch b/media/libcubeb/fix-crashes.patch
deleted file mode 100644
index b23501fcfb..0000000000
--- a/media/libcubeb/fix-crashes.patch
+++ /dev/null
@@ -1,71 +0,0 @@
-This patch fixes three different crashes, one crash per chunk in this patch,
-in the same order.
-- Bug 1342389
-- Bug 1345147
-- Bug 1347453
-
-diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp
---- a/media/libcubeb/src/cubeb_wasapi.cpp
-+++ b/media/libcubeb/src/cubeb_wasapi.cpp
-@@ -878,16 +878,23 @@ wasapi_stream_render_loop(LPVOID stream)
-
- /* WaitForMultipleObjects timeout can trigger in cases where we don't want to
- treat it as a timeout, such as across a system sleep/wake cycle. Trigger
- the timeout error handling only when the timeout_limit is reached, which is
- reset on each successful loop. */
- unsigned timeout_count = 0;
- const unsigned timeout_limit = 5;
- while (is_playing) {
-+ // We want to check the emergency bailout variable before a
-+ // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects
-+ // is going to wait on might have been closed already.
-+ if (*emergency_bailout) {
-+ delete emergency_bailout;
-+ return 0;
-+ }
- DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
- wait_array,
- FALSE,
- 1000);
- if (*emergency_bailout) {
- delete emergency_bailout;
- return 0;
- }
-@@ -1199,16 +1206,22 @@ bool stop_and_join_render_thread(cubeb_s
- {
- bool rv = true;
- LOG("Stop and join render thread.");
- if (!stm->thread) {
- LOG("No thread present.");
- return true;
- }
-
-+ // If we've already leaked the thread, just return,
-+ // there is not much we can do.
-+ if (!stm->emergency_bailout.load()) {
-+ return false;
-+ }
-+
- BOOL ok = SetEvent(stm->shutdown_event);
- if (!ok) {
- LOG("Destroy SetEvent failed: %lx", GetLastError());
- }
-
- /* Wait five seconds for the rendering thread to return. It's supposed to
- * check its event loop very often, five seconds is rather conservative. */
- DWORD r = WaitForSingleObject(stm->thread, 5000);
-diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh
---- a/media/libcubeb/update.sh
-+++ b/media/libcubeb/update.sh
-@@ -66,8 +66,11 @@ fi
- echo "Applying a patch on top of $version"
- patch -p1 < ./wasapi-drift-fix-passthrough-resampler.patch
-
- echo "Applying a patch on top of $version"
- patch -p1 < ./audiounit-drift-fix.patch
-
- echo "Applying a patch on top of $version"
- patch -p1 < ./uplift-wasapi-fixes-aurora.patch
-+
-+echo "Applying a patch on top of $version"
-+patch -p3 < ./fix-crashes.patch
diff --git a/media/libcubeb/gtest/common.h b/media/libcubeb/gtest/common.h
new file mode 100644
index 0000000000..f085d8115f
--- /dev/null
+++ b/media/libcubeb/gtest/common.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright © 2013 Sebastien Alaiwan
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#if !defined(TEST_COMMON)
+#define TEST_COMMON
+
+#if defined( _WIN32)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <objbase.h>
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include <cstdarg>
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+
+template<typename T, size_t N>
+constexpr size_t
+ARRAY_LENGTH(T(&)[N])
+{
+ return N;
+}
+
+void delay(unsigned int ms)
+{
+#if defined(_WIN32)
+ Sleep(ms);
+#else
+ sleep(ms / 1000);
+ usleep(ms % 1000 * 1000);
+#endif
+}
+
+#if !defined(M_PI)
+#define M_PI 3.14159265358979323846
+#endif
+
+typedef struct {
+ char const * name;
+ unsigned int const channels;
+ uint32_t const layout;
+} layout_info;
+
+int has_available_input_device(cubeb * ctx)
+{
+ cubeb_device_collection devices;
+ int input_device_available = 0;
+ int r;
+ /* Bail out early if the host does not have input devices. */
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "error enumerating devices.");
+ return 0;
+ }
+
+ if (devices.count == 0) {
+ fprintf(stderr, "no input device available, skipping test.\n");
+ cubeb_device_collection_destroy(ctx, &devices);
+ return 0;
+ }
+
+ for (uint32_t i = 0; i < devices.count; i++) {
+ input_device_available |= (devices.device[i].state ==
+ CUBEB_DEVICE_STATE_ENABLED);
+ }
+
+ if (!input_device_available) {
+ fprintf(stderr, "there are input devices, but they are not "
+ "available, skipping\n");
+ }
+
+ cubeb_device_collection_destroy(ctx, &devices);
+ return !!input_device_available;
+}
+
+void print_log(const char * msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ vprintf(msg, args);
+ va_end(args);
+}
+
+/** Initialize cubeb with backend override.
+ * Create call cubeb_init passing value for CUBEB_BACKEND env var as
+ * override. */
+int common_init(cubeb ** ctx, char const * ctx_name)
+{
+#ifdef ENABLE_NORMAL_LOG
+ if (cubeb_set_log_callback(CUBEB_LOG_NORMAL, print_log) != CUBEB_OK) {
+ fprintf(stderr, "Set normal log callback failed\n");
+ }
+#endif
+
+#ifdef ENABLE_VERBOSE_LOG
+ if (cubeb_set_log_callback(CUBEB_LOG_VERBOSE, print_log) != CUBEB_OK) {
+ fprintf(stderr, "Set verbose log callback failed\n");
+ }
+#endif
+
+ int r;
+ char const * backend;
+ char const * ctx_backend;
+
+ backend = getenv("CUBEB_BACKEND");
+ r = cubeb_init(ctx, ctx_name, backend);
+ if (r == CUBEB_OK && backend) {
+ ctx_backend = cubeb_get_backend_id(*ctx);
+ if (strcmp(backend, ctx_backend) != 0) {
+ fprintf(stderr, "Requested backend `%s', got `%s'\n",
+ backend, ctx_backend);
+ }
+ }
+
+ return r;
+}
+
+#if defined( _WIN32)
+class TestEnvironment : public ::testing::Environment {
+public:
+ void SetUp() override {
+ hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ }
+
+ void TearDown() override {
+ if (SUCCEEDED(hr)) {
+ CoUninitialize();
+ }
+ }
+
+private:
+ HRESULT hr;
+};
+
+::testing::Environment* const foo_env = ::testing::AddGlobalTestEnvironment(new TestEnvironment);
+#endif
+
+#endif /* TEST_COMMON */
diff --git a/media/libcubeb/gtest/test_audio.cpp b/media/libcubeb/gtest/test_audio.cpp
new file mode 100644
index 0000000000..ea1b999c9b
--- /dev/null
+++ b/media/libcubeb/gtest/test_audio.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright © 2013 Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function exhaustive test. Plays a series of tones in different
+ * conditions. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include <string.h>
+#include "cubeb/cubeb.h"
+#include <string>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+using namespace std;
+
+#define MAX_NUM_CHANNELS 32
+
+#if !defined(M_PI)
+#define M_PI 3.14159265358979323846
+#endif
+
+#define VOLUME 0.2
+
+float get_frequency(int channel_index)
+{
+ return 220.0f * (channel_index+1);
+}
+
+template<typename T> T ConvertSample(double input);
+template<> float ConvertSample(double input) { return input; }
+template<> short ConvertSample(double input) { return short(input * 32767.0f); }
+
+/* store the phase of the generated waveform */
+struct synth_state {
+ synth_state(int num_channels_, float sample_rate_)
+ : num_channels(num_channels_),
+ sample_rate(sample_rate_)
+ {
+ for(int i=0;i < MAX_NUM_CHANNELS;++i)
+ phase[i] = 0.0f;
+ }
+
+ template<typename T>
+ void run(T* audiobuffer, long nframes)
+ {
+ for(int c=0;c < num_channels;++c) {
+ float freq = get_frequency(c);
+ float phase_inc = 2.0 * M_PI * freq / sample_rate;
+ for(long n=0;n < nframes;++n) {
+ audiobuffer[n*num_channels+c] = ConvertSample<T>(sin(phase[c]) * VOLUME);
+ phase[c] += phase_inc;
+ }
+ }
+ }
+
+private:
+ int num_channels;
+ float phase[MAX_NUM_CHANNELS];
+ float sample_rate;
+};
+
+template<typename T>
+long data_cb(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
+{
+ synth_state *synth = (synth_state *)user;
+ synth->run((T*)outputbuffer, nframes);
+ return nframes;
+}
+
+void state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
+{
+}
+
+/* Our android backends don't support float, only int16. */
+int supports_float32(string backend_id)
+{
+ return backend_id != "opensl"
+ && backend_id != "audiotrack";
+}
+
+/* Some backends don't have code to deal with more than mono or stereo. */
+int supports_channel_count(string backend_id, int nchannels)
+{
+ return nchannels <= 2 ||
+ (backend_id != "opensl" && backend_id != "audiotrack");
+}
+
+int run_test(int num_channels, int sampling_rate, int is_float)
+{
+ int r = CUBEB_OK;
+
+ cubeb *ctx = NULL;
+
+ r = common_init(&ctx, "Cubeb audio test: channels");
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb library\n");
+ return r;
+ }
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ const char * backend_id = cubeb_get_backend_id(ctx);
+
+ if ((is_float && !supports_float32(backend_id)) ||
+ !supports_channel_count(backend_id, num_channels)) {
+ /* don't treat this as a test failure. */
+ return CUBEB_OK;
+ }
+
+ fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
+
+ cubeb_stream_params params;
+ params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
+ params.rate = sampling_rate;
+ params.channels = num_channels;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ synth_state synth(params.channels, params.rate);
+
+ cubeb_stream *stream = NULL;
+ r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
+ 4096, is_float ? &data_cb<float> : &data_cb<short>, state_cb_audio, &synth);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
+ return r;
+ }
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(200);
+ cubeb_stream_stop(stream);
+
+ return r;
+}
+
+int run_volume_test(int is_float)
+{
+ int r = CUBEB_OK;
+
+ cubeb *ctx = NULL;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb library\n");
+ return r;
+ }
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ const char * backend_id = cubeb_get_backend_id(ctx);
+
+ if ((is_float && !supports_float32(backend_id))) {
+ /* don't treat this as a test failure. */
+ return CUBEB_OK;
+ }
+
+ cubeb_stream_params params;
+ params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
+ params.rate = 44100;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ synth_state synth(params.channels, params.rate);
+
+ cubeb_stream *stream = NULL;
+ r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
+ 4096, is_float ? &data_cb<float> : &data_cb<short>,
+ state_cb_audio, &synth);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
+ return r;
+ }
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ fprintf(stderr, "Testing: volume\n");
+ for(int i=0;i <= 4; ++i)
+ {
+ fprintf(stderr, "Volume: %d%%\n", i*25);
+
+ cubeb_stream_set_volume(stream, i/4.0f);
+ cubeb_stream_start(stream);
+ delay(400);
+ cubeb_stream_stop(stream);
+ delay(100);
+ }
+
+ return r;
+}
+
+TEST(cubeb, run_volume_test_short)
+{
+ ASSERT_EQ(run_volume_test(0), CUBEB_OK);
+}
+
+TEST(cubeb, run_volume_test_float)
+{
+ ASSERT_EQ(run_volume_test(1), CUBEB_OK);
+}
+
+TEST(cubeb, run_channel_rate_test)
+{
+ unsigned int channel_values[] = {
+ 1,
+ 2,
+ 3,
+ 4,
+ 6,
+ };
+
+ int freq_values[] = {
+ 16000,
+ 24000,
+ 44100,
+ 48000,
+ };
+
+ for(auto channels : channel_values) {
+ for(auto freq : freq_values) {
+ ASSERT_TRUE(channels < MAX_NUM_CHANNELS);
+ fprintf(stderr, "--------------------------\n");
+ ASSERT_EQ(run_test(channels, freq, 0), CUBEB_OK);
+ ASSERT_EQ(run_test(channels, freq, 1), CUBEB_OK);
+ }
+ }
+}
diff --git a/media/libcubeb/gtest/test_devices.cpp b/media/libcubeb/gtest/test_devices.cpp
new file mode 100644
index 0000000000..e9b34b3245
--- /dev/null
+++ b/media/libcubeb/gtest/test_devices.cpp
@@ -0,0 +1,255 @@
+/*
+ * Copyright © 2015 Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb enumerate device test/example.
+ * Prints out a list of devices enumerated. */
+#include "gtest/gtest.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ // noop, unused
+ return 0;
+}
+
+void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ // noop, unused
+}
+
+static void
+print_device_info(cubeb_device_info * info, FILE * f)
+{
+ char devfmts[64] = "";
+ const char * devtype, * devstate, * devdeffmt;
+
+ switch (info->type) {
+ case CUBEB_DEVICE_TYPE_INPUT:
+ devtype = "input";
+ break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ devtype = "output";
+ break;
+ case CUBEB_DEVICE_TYPE_UNKNOWN:
+ default:
+ devtype = "unknown?";
+ break;
+ };
+
+ switch (info->state) {
+ case CUBEB_DEVICE_STATE_DISABLED:
+ devstate = "disabled";
+ break;
+ case CUBEB_DEVICE_STATE_UNPLUGGED:
+ devstate = "unplugged";
+ break;
+ case CUBEB_DEVICE_STATE_ENABLED:
+ devstate = "enabled";
+ break;
+ default:
+ devstate = "unknown?";
+ break;
+ };
+
+ switch (info->default_format) {
+ case CUBEB_DEVICE_FMT_S16LE:
+ devdeffmt = "S16LE";
+ break;
+ case CUBEB_DEVICE_FMT_S16BE:
+ devdeffmt = "S16BE";
+ break;
+ case CUBEB_DEVICE_FMT_F32LE:
+ devdeffmt = "F32LE";
+ break;
+ case CUBEB_DEVICE_FMT_F32BE:
+ devdeffmt = "F32BE";
+ break;
+ default:
+ devdeffmt = "unknown?";
+ break;
+ };
+
+ if (info->format & CUBEB_DEVICE_FMT_S16LE)
+ strcat(devfmts, " S16LE");
+ if (info->format & CUBEB_DEVICE_FMT_S16BE)
+ strcat(devfmts, " S16BE");
+ if (info->format & CUBEB_DEVICE_FMT_F32LE)
+ strcat(devfmts, " F32LE");
+ if (info->format & CUBEB_DEVICE_FMT_F32BE)
+ strcat(devfmts, " F32BE");
+
+ fprintf(f,
+ "dev: \"%s\"%s\n"
+ "\tName: \"%s\"\n"
+ "\tGroup: \"%s\"\n"
+ "\tVendor: \"%s\"\n"
+ "\tType: %s\n"
+ "\tState: %s\n"
+ "\tCh: %u\n"
+ "\tFormat: %s (0x%x) (default: %s)\n"
+ "\tRate: %u - %u (default: %u)\n"
+ "\tLatency: lo %u frames, hi %u frames\n",
+ info->device_id, info->preferred ? " (PREFERRED)" : "",
+ info->friendly_name, info->group_id, info->vendor_name,
+ devtype, devstate, info->max_channels,
+ (devfmts[0] == '\0') ? devfmts : devfmts + 1,
+ (unsigned int)info->format, devdeffmt,
+ info->min_rate, info->max_rate, info->default_rate,
+ info->latency_lo, info->latency_hi);
+}
+
+static void
+print_device_collection(cubeb_device_collection * collection, FILE * f)
+{
+ uint32_t i;
+
+ for (i = 0; i < collection->count; i++)
+ print_device_info(&collection->device[i], f);
+}
+
+TEST(cubeb, destroy_default_collection)
+{
+ int r;
+ cubeb * ctx = NULL;
+ cubeb_device_collection collection{ nullptr, 0 };
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ ASSERT_EQ(collection.device, nullptr);
+ ASSERT_EQ(collection.count, (size_t) 0);
+
+ r = cubeb_device_collection_destroy(ctx, &collection);
+ if (r != CUBEB_ERROR_NOT_SUPPORTED) {
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(collection.device, nullptr);
+ ASSERT_EQ(collection.count, (size_t) 0);
+ }
+}
+
+TEST(cubeb, enumerate_devices)
+{
+ int r;
+ cubeb * ctx = NULL;
+ cubeb_device_collection collection;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ fprintf(stdout, "Enumerating input devices for backend %s\n",
+ cubeb_get_backend_id(ctx));
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Device enumeration not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+
+ fprintf(stdout, "Found %zu input devices\n", collection.count);
+ print_device_collection(&collection, stdout);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ fprintf(stdout, "Enumerating output devices for backend %s\n",
+ cubeb_get_backend_id(ctx));
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+
+ fprintf(stdout, "Found %zu output devices\n", collection.count);
+ print_device_collection(&collection, stdout);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ uint32_t count_before_creating_duplex_stream;
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ count_before_creating_duplex_stream = collection.count;
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = output_params.rate = 48000;
+ input_params.channels = output_params.channels = 1;
+ input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ 1024, data_cb_duplex, state_cb_duplex, nullptr);
+
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ ASSERT_EQ(count_before_creating_duplex_stream, collection.count);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ cubeb_stream_destroy(stream);
+}
+
+TEST(cubeb, stream_get_current_device)
+{
+ cubeb * ctx = NULL;
+ int r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ fprintf(stdout, "Getting current devices for backend %s\n",
+ cubeb_get_backend_id(ctx));
+
+ cubeb_stream * stream = NULL;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = output_params.rate = 48000;
+ input_params.channels = output_params.channels = 1;
+ input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ 1024, data_cb_duplex, state_cb_duplex, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_device * device;
+ r = cubeb_stream_get_current_device(stream, &device);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Getting current device is not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error getting current devices";
+
+ fprintf(stdout, "Current output device: %s\n", device->output_name);
+ fprintf(stdout, "Current input device: %s\n", device->input_name);
+
+ r = cubeb_stream_device_destroy(stream, device);
+ ASSERT_EQ(r, CUBEB_OK) << "Error destroying current devices";
+} \ No newline at end of file
diff --git a/media/libcubeb/gtest/test_duplex.cpp b/media/libcubeb/gtest/test_duplex.cpp
new file mode 100644
index 0000000000..3620bf0666
--- /dev/null
+++ b/media/libcubeb/gtest/test_duplex.cpp
@@ -0,0 +1,315 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Loops input back to output and check audio
+ * is flowing. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+#define INPUT_CHANNELS 1
+#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
+#define OUTPUT_CHANNELS 2
+#define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO
+
+struct user_state_duplex
+{
+ std::atomic<int> invalid_audio_value{ 0 };
+};
+
+long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ user_state_duplex * u = reinterpret_cast<user_state_duplex*>(user);
+ float *ib = (float *)inputbuffer;
+ float *ob = (float *)outputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ // Loop back: upmix the single input channel to the two output channels,
+ // checking if there is noise in the process.
+ long output_index = 0;
+ for (long i = 0; i < nframes; i++) {
+ if (ib[i] <= -1.0 || ib[i] >= 1.0) {
+ u->invalid_audio_value = 1;
+ break;
+ }
+ ob[output_index] = ob[output_index + 1] = ib[i];
+ output_index += 2;
+ }
+
+ return nframes;
+}
+
+void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+TEST(cubeb, duplex)
+{
+ cubeb *ctx;
+ cubeb_stream *stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ user_state_duplex stream_state;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!has_available_input_device(ctx)) {
+ return;
+ }
+
+ /* typical user-case: mono input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = INPUT_LAYOUT;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ latency_frames, data_cb_duplex, state_cb_duplex, &stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ cubeb_stream_stop(stream);
+
+ ASSERT_FALSE(stream_state.invalid_audio_value.load());
+}
+
+void device_collection_changed_callback(cubeb * context, void * user)
+{
+ fprintf(stderr, "collection changed callback\n");
+ ASSERT_TRUE(false) << "Error: device collection changed callback"
+ " called when opening a stream";
+}
+
+TEST(cubeb, duplex_collection_change)
+{
+ cubeb *ctx;
+ cubeb_stream *stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example with collection change");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ r = cubeb_register_device_collection_changed(ctx,
+ static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT),
+ device_collection_changed_callback,
+ nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* typical user-case: mono input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = INPUT_LAYOUT;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ latency_frames, data_cb_duplex, state_cb_duplex, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+ cubeb_stream_destroy(stream);
+}
+
+long data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
+ return CUBEB_ERROR;
+ }
+
+ return nframes;
+}
+
+void state_cb_input(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ case CUBEB_STATE_ERROR:
+ fprintf(stderr, "stream runs into error state\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+std::vector<cubeb_devid> get_devices(cubeb * ctx, cubeb_device_type type) {
+ std::vector<cubeb_devid> devices;
+
+ cubeb_device_collection collection;
+ int r = cubeb_enumerate_devices(ctx, type, &collection);
+
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Failed to enumerate devices\n");
+ return devices;
+ }
+
+ for (uint32_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].state == CUBEB_DEVICE_STATE_ENABLED) {
+ devices.emplace_back(collection.device[i].devid);
+ }
+ }
+
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ return devices;
+}
+
+TEST(cubeb, one_duplex_one_input)
+{
+ cubeb *ctx;
+ cubeb_stream *duplex_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ user_state_duplex duplex_stream_state;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* This test needs at least two available input devices. */
+ std::vector<cubeb_devid> input_devices = get_devices(ctx, CUBEB_DEVICE_TYPE_INPUT);
+ if (input_devices.size() < 2) {
+ return;
+ }
+
+ /* This test needs at least one available output device. */
+ std::vector<cubeb_devid> output_devices = get_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_devices.size() < 1) {
+ return;
+ }
+
+ cubeb_devid duplex_input = input_devices.front();
+ cubeb_devid duplex_output = nullptr; // default device
+ cubeb_devid input_only = input_devices.back();
+
+ /* typical use-case: mono voice input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = CUBEB_STREAM_PREF_VOICE;
+
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &duplex_stream, "Cubeb duplex",
+ duplex_input, &input_params, duplex_output, &output_params,
+ latency_frames, data_cb_duplex, state_cb_duplex, &duplex_stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing duplex cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(duplex_stream, cubeb_stream_destroy);
+
+ r = cubeb_stream_start(duplex_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not start duplex stream";
+ delay(500);
+
+ cubeb_stream *input_stream;
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb input",
+ input_only, &input_params, NULL, NULL,
+ latency_frames, data_cb_input, state_cb_input, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing input-only cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ r = cubeb_stream_start(input_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not start input stream";
+ delay(500);
+
+ r = cubeb_stream_stop(duplex_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not stop duplex stream";
+
+ r = cubeb_stream_stop(input_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not stop input stream";
+
+ ASSERT_FALSE(duplex_stream_state.invalid_audio_value.load());
+}
diff --git a/media/libcubeb/gtest/test_latency.cpp b/media/libcubeb/gtest/test_latency.cpp
new file mode 100644
index 0000000000..522851044a
--- /dev/null
+++ b/media/libcubeb/gtest/test_latency.cpp
@@ -0,0 +1,47 @@
+#include "gtest/gtest.h"
+#include <stdlib.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+TEST(cubeb, latency)
+{
+ cubeb * ctx = NULL;
+ int r;
+ uint32_t max_channels;
+ uint32_t preferred_rate;
+ uint32_t latency_frames;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ r = cubeb_get_max_channel_count(ctx, &max_channels);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(max_channels, 0u);
+ }
+
+ r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(preferred_rate, 0u);
+ }
+
+ cubeb_stream_params params = {
+ CUBEB_SAMPLE_FLOAT32NE,
+ preferred_rate,
+ max_channels,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE
+ };
+ r = cubeb_get_min_latency(ctx, &params, &latency_frames);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(latency_frames, 0u);
+ }
+}
diff --git a/media/libcubeb/gtest/test_loopback.cpp b/media/libcubeb/gtest/test_loopback.cpp
new file mode 100644
index 0000000000..9977f6f934
--- /dev/null
+++ b/media/libcubeb/gtest/test_loopback.cpp
@@ -0,0 +1,578 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+ /* libcubeb api/function test. Requests a loopback device and checks that
+ output is being looped back to input. NOTE: Usage of output devices while
+ performing this test will cause flakey results! */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <string>
+#include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+const uint32_t SAMPLE_FREQUENCY = 48000;
+const uint32_t TONE_FREQUENCY = 440;
+const double OUTPUT_AMPLITUDE = 0.25;
+const int32_t NUM_FRAMES_TO_OUTPUT = SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
+
+template<typename T> T ConvertSampleToOutput(double input);
+template<> float ConvertSampleToOutput(double input) { return float(input); }
+template<> short ConvertSampleToOutput(double input) { return short(input * 32767.0f); }
+
+template<typename T> double ConvertSampleFromOutput(T sample);
+template<> double ConvertSampleFromOutput(float sample) { return double(sample); }
+template<> double ConvertSampleFromOutput(short sample) { return double(sample / 32767.0); }
+
+/* Simple cross correlation to help find phase shift. Not a performant impl */
+std::vector<double> cross_correlate(std::vector<double> & f,
+ std::vector<double> & g,
+ size_t signal_length)
+{
+ /* the length we sweep our window through to find the cross correlation */
+ size_t sweep_length = f.size() - signal_length + 1;
+ std::vector<double> correlation;
+ correlation.reserve(sweep_length);
+ for (size_t i = 0; i < sweep_length; i++) {
+ double accumulator = 0.0;
+ for (size_t j = 0; j < signal_length; j++) {
+ accumulator += f.at(j) * g.at(i + j);
+ }
+ correlation.push_back(accumulator);
+ }
+ return correlation;
+}
+
+/* best effort discovery of phase shift between output and (looped) input*/
+size_t find_phase(std::vector<double> & output_frames,
+ std::vector<double> & input_frames,
+ size_t signal_length)
+{
+ std::vector<double> correlation = cross_correlate(output_frames, input_frames, signal_length);
+ size_t phase = 0;
+ double max_correlation = correlation.at(0);
+ for (size_t i = 1; i < correlation.size(); i++) {
+ if (correlation.at(i) > max_correlation) {
+ max_correlation = correlation.at(i);
+ phase = i;
+ }
+ }
+ return phase;
+}
+
+std::vector<double> normalize_frames(std::vector<double> & frames) {
+ double max = abs(*std::max_element(frames.begin(), frames.end(),
+ [](double a, double b) { return abs(a) < abs(b); }));
+ std::vector<double> normalized_frames;
+ normalized_frames.reserve(frames.size());
+ for (const double frame : frames) {
+ normalized_frames.push_back(frame / max);
+ }
+ return normalized_frames;
+}
+
+/* heuristic comparison of aligned output and input signals, gets flaky if TONE_FREQUENCY is too high */
+void compare_signals(std::vector<double> & output_frames,
+ std::vector<double> & input_frames)
+{
+ ASSERT_EQ(output_frames.size(), input_frames.size()) << "#Output frames != #input frames";
+ size_t num_frames = output_frames.size();
+ std::vector<double> normalized_output_frames = normalize_frames(output_frames);
+ std::vector<double> normalized_input_frames = normalize_frames(input_frames);
+
+ /* calculate mean absolute errors */
+ /* mean absolute errors between output and input */
+ double io_mas = 0.0;
+ /* mean absolute errors between output and silence */
+ double output_silence_mas = 0.0;
+ /* mean absolute errors between input and silence */
+ double input_silence_mas = 0.0;
+ for (size_t i = 0; i < num_frames; i++) {
+ io_mas += abs(normalized_output_frames.at(i) - normalized_input_frames.at(i));
+ output_silence_mas += abs(normalized_output_frames.at(i));
+ input_silence_mas += abs(normalized_input_frames.at(i));
+ }
+ io_mas /= num_frames;
+ output_silence_mas /= num_frames;
+ input_silence_mas /= num_frames;
+
+ ASSERT_LT(io_mas, output_silence_mas)
+ << "Error between output and input should be less than output and silence!";
+ ASSERT_LT(io_mas, input_silence_mas)
+ << "Error between output and input should be less than output and silence!";
+
+ /* make sure extrema are in (roughly) correct location */
+ /* number of maxima + minama expected in the frames*/
+ const long NUM_EXTREMA = 2 * TONE_FREQUENCY * NUM_FRAMES_TO_OUTPUT / SAMPLE_FREQUENCY;
+ /* expected index of first maxima */
+ const long FIRST_MAXIMUM_INDEX = SAMPLE_FREQUENCY / TONE_FREQUENCY / 4;
+ /* Threshold we expect all maxima and minima to be above or below. Ideally
+ the extrema would be 1 or -1, but particularly at the start of loopback
+ the values seen can be significantly lower. */
+ const double THRESHOLD = 0.5;
+
+ for (size_t i = 0; i < NUM_EXTREMA; i++) {
+ bool is_maximum = i % 2 == 0;
+ /* expected offset to current extreme: i * stide between extrema */
+ size_t offset = i * SAMPLE_FREQUENCY / TONE_FREQUENCY / 2;
+ if (is_maximum) {
+ ASSERT_GT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
+ << "Output frames have unexpected missing maximum!";
+ ASSERT_GT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
+ << "Input frames have unexpected missing maximum!";
+ } else {
+ ASSERT_LT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
+ << "Output frames have unexpected missing minimum!";
+ ASSERT_LT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
+ << "Input frames have unexpected missing minimum!";
+ }
+ }
+}
+
+struct user_state_loopback {
+ std::mutex user_state_mutex;
+ long position = 0;
+ /* track output */
+ std::vector<double> output_frames;
+ /* track input */
+ std::vector<double> input_frames;
+};
+
+template<typename T>
+long data_cb_loop_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ib = (T *) inputbuffer;
+ T * ob = (T *) outputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ /* generate our test tone on the fly */
+ for (int i = 0; i < nframes; i++) {
+ double tone = 0.0;
+ if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+ /* generate sine wave */
+ tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+ tone *= OUTPUT_AMPLITUDE;
+ }
+ ob[i] = ConvertSampleToOutput<T>(tone);
+ u->output_frames.push_back(tone);
+ /* store any looped back output, may be silence */
+ u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+ }
+
+ u->position += nframes;
+
+ return nframes;
+}
+
+template<typename T>
+long data_cb_loop_input_only(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ib = (T *) inputbuffer;
+
+ if (outputbuffer != NULL) {
+ // Can't assert as it needs to return, so expect to fail instead
+ EXPECT_EQ(outputbuffer, (void *) NULL) << "outputbuffer should be null in input only callback";
+ return CUBEB_ERROR;
+ }
+
+ if (stream == NULL || inputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ for (int i = 0; i < nframes; i++) {
+ u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+ }
+
+ return nframes;
+}
+
+template<typename T>
+long data_cb_playback(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ob = (T *) outputbuffer;
+
+ if (stream == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ /* generate our test tone on the fly */
+ for (int i = 0; i < nframes; i++) {
+ double tone = 0.0;
+ if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+ /* generate sine wave */
+ tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+ tone *= OUTPUT_AMPLITUDE;
+ }
+ ob[i] = ConvertSampleToOutput<T>(tone);
+ u->output_frames.push_back(tone);
+ }
+
+ u->position += nframes;
+
+ return nframes;
+}
+
+void state_cb_loop(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+void run_loopback_duplex_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: duplex stream");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup a duplex stream with loopback */
+ r = cubeb_stream_init(ctx, &stream, "Cubeb loopback",
+ NULL, &input_params, NULL, &output_params, latency_frames,
+ is_float ? data_cb_loop_duplex<float> : data_cb_loop_duplex<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(300);
+ cubeb_stream_stop(stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_EQ(output_frames.size(), input_frames.size())
+ << "#Output frames != #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_duplex)
+{
+ run_loopback_duplex_test(true);
+ run_loopback_duplex_test(false);
+}
+
+void run_loopback_separate_streams_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * input_stream;
+ cubeb_stream * output_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: separate streams");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ NULL, &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ /* setup an output stream */
+ r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
+ NULL, NULL, NULL, &output_params, latency_frames,
+ is_float ? data_cb_playback<float> : data_cb_playback<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ cubeb_stream_start(output_stream);
+ delay(300);
+ cubeb_stream_stop(output_stream);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_LE(output_frames.size(), input_frames.size())
+ << "#Output frames should be less or equal to #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_separate_streams)
+{
+ run_loopback_separate_streams_test(true);
+ run_loopback_separate_streams_test(false);
+}
+
+void run_loopback_silence_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * input_stream;
+ cubeb_stream_params input_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: record silence");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ NULL, &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ delay(300);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & input_frames = user_data->input_frames;
+
+ /* expect to have at least ~50ms of frames */
+ ASSERT_GE(input_frames.size(), SAMPLE_FREQUENCY / 20);
+ double EPISILON = 0.0001;
+ /* frames should be 0.0, but use epsilon to avoid possible issues with impls
+ that may use ~0.0 silence values. */
+ for (double frame : input_frames) {
+ ASSERT_LT(abs(frame), EPISILON);
+ }
+}
+
+TEST(cubeb, loopback_silence)
+{
+ run_loopback_silence_test(true);
+ run_loopback_silence_test(false);
+}
+
+void run_loopback_device_selection_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_device_collection collection;
+ cubeb_stream * input_stream;
+ cubeb_stream * output_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: device selection, separate streams");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Device enumeration not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ /* get first preferred output device id */
+ std::string device_id;
+ for (size_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].preferred) {
+ device_id = collection.device[i].device_id;
+ break;
+ }
+ }
+ cubeb_device_collection_destroy(ctx, &collection);
+ if (device_id.empty()) {
+ fprintf(stderr, "Could not find preferred device, aborting test.\n");
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ device_id.c_str(), &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ /* setup an output stream */
+ r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
+ NULL, NULL, device_id.c_str(), &output_params, latency_frames,
+ is_float ? data_cb_playback<float> : data_cb_playback<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ cubeb_stream_start(output_stream);
+ delay(300);
+ cubeb_stream_stop(output_stream);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_LE(output_frames.size(), input_frames.size())
+ << "#Output frames should be less or equal to #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_device_selection)
+{
+ run_loopback_device_selection_test(true);
+ run_loopback_device_selection_test(false);
+}
diff --git a/media/libcubeb/gtest/test_overload_callback.cpp b/media/libcubeb/gtest/test_overload_callback.cpp
new file mode 100644
index 0000000000..4a19ce9f13
--- /dev/null
+++ b/media/libcubeb/gtest/test_overload_callback.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include <atomic>
+#include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
+
+std::atomic<bool> load_callback{ false };
+
+long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ if (load_callback) {
+ fprintf(stderr, "Sleeping...\n");
+ delay(100000);
+ fprintf(stderr, "Sleeping done\n");
+ }
+ return nframes;
+}
+
+void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ ASSERT_TRUE(!!stream);
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ FAIL() << "this test is not supposed to drain"; break;
+ case CUBEB_STATE_ERROR:
+ fprintf(stderr, "stream error\n"); break;
+ default:
+ FAIL() << "this test is not supposed to have a weird state"; break;
+ }
+}
+
+TEST(cubeb, overload_callback)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb callback overload");
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = 48000;
+ output_params.channels = 2;
+ output_params.layout = CUBEB_LAYOUT_STEREO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb",
+ NULL, NULL, NULL, &output_params,
+ latency_frames, data_cb, state_cb, NULL);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ // This causes the callback to sleep for a large number of seconds.
+ load_callback = true;
+ delay(500);
+ cubeb_stream_stop(stream);
+}
diff --git a/media/libcubeb/gtest/test_record.cpp b/media/libcubeb/gtest/test_record.cpp
new file mode 100644
index 0000000000..ed40a2c27d
--- /dev/null
+++ b/media/libcubeb/gtest/test_record.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Record the mic and check there is sound. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+
+struct user_state_record
+{
+ std::atomic<int> invalid_audio_value{ 0 };
+};
+
+long data_cb_record(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ user_state_record * u = reinterpret_cast<user_state_record*>(user);
+ float *b = (float *)inputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
+ return CUBEB_ERROR;
+ }
+
+ for (long i = 0; i < nframes; i++) {
+ if (b[i] <= -1.0 || b[i] >= 1.0) {
+ u->invalid_audio_value = 1;
+ break;
+ }
+ }
+
+ return nframes;
+}
+
+void state_cb_record(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+TEST(cubeb, record)
+{
+ if (cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr /*print_log*/) != CUBEB_OK) {
+ fprintf(stderr, "Set log callback failed\n");
+ }
+ cubeb *ctx;
+ cubeb_stream *stream;
+ cubeb_stream_params params;
+ int r;
+ user_state_record stream_state;
+
+ r = common_init(&ctx, "Cubeb record example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!has_available_input_device(ctx)) {
+ return;
+ }
+
+ params.format = STREAM_FORMAT;
+ params.rate = SAMPLE_FREQUENCY;
+ params.channels = 1;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, &params, NULL, nullptr,
+ 4096, data_cb_record, state_cb_record, &stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ cubeb_stream_stop(stream);
+
+#ifdef __linux__
+ // user callback does not arrive in Linux, silence the error
+ fprintf(stderr, "Check is disabled in Linux\n");
+#else
+ ASSERT_FALSE(stream_state.invalid_audio_value.load());
+#endif
+}
diff --git a/media/libcubeb/gtest/test_resampler.cpp b/media/libcubeb/gtest/test_resampler.cpp
new file mode 100644
index 0000000000..8ac878fc3d
--- /dev/null
+++ b/media/libcubeb/gtest/test_resampler.cpp
@@ -0,0 +1,1081 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
+#include "gtest/gtest.h"
+#include "common.h"
+#include "cubeb_resampler_internal.h"
+#include <stdio.h>
+#include <algorithm>
+#include <iostream>
+
+/* Windows cmath USE_MATH_DEFINE thing... */
+const float PI = 3.14159265359f;
+
+/* Testing all sample rates is very long, so if THOROUGH_TESTING is not defined,
+ * only part of the test suite is ran. */
+#ifdef THOROUGH_TESTING
+/* Some standard sample rates we're testing with. */
+const uint32_t sample_rates[] = {
+ 8000,
+ 16000,
+ 32000,
+ 44100,
+ 48000,
+ 88200,
+ 96000,
+ 192000
+};
+/* The maximum number of channels we're resampling. */
+const uint32_t max_channels = 2;
+/* The minimum an maximum number of milliseconds we're resampling for. This is
+ * used to simulate the fact that the audio stream is resampled in chunks,
+ * because audio is delivered using callbacks. */
+const uint32_t min_chunks = 10; /* ms */
+const uint32_t max_chunks = 30; /* ms */
+const uint32_t chunk_increment = 1;
+
+#else
+
+const uint32_t sample_rates[] = {
+ 8000,
+ 44100,
+ 48000,
+};
+const uint32_t max_channels = 2;
+const uint32_t min_chunks = 10; /* ms */
+const uint32_t max_chunks = 30; /* ms */
+const uint32_t chunk_increment = 10;
+#endif
+
+#define DUMP_ARRAYS
+#ifdef DUMP_ARRAYS
+/**
+ * Files produced by dump(...) can be converted to .wave files using:
+ *
+ * sox -c <channel_count> -r <rate> -e float -b 32 file.raw file.wav
+ *
+ * for floating-point audio, or:
+ *
+ * sox -c <channel_count> -r <rate> -e unsigned -b 16 file.raw file.wav
+ *
+ * for 16bit integer audio.
+ */
+
+/* Use the correct implementation of fopen, depending on the platform. */
+void fopen_portable(FILE ** f, const char * name, const char * mode)
+{
+#ifdef WIN32
+ fopen_s(f, name, mode);
+#else
+ *f = fopen(name, mode);
+#endif
+}
+
+template<typename T>
+void dump(const char * name, T * frames, size_t count)
+{
+ FILE * file;
+ fopen_portable(&file, name, "wb");
+
+ if (!file) {
+ fprintf(stderr, "error opening %s\n", name);
+ return;
+ }
+
+ if (count != fwrite(frames, sizeof(T), count, file)) {
+ fprintf(stderr, "error writing to %s\n", name);
+ }
+ fclose(file);
+}
+#else
+template<typename T>
+void dump(const char * name, T * frames, size_t count)
+{ }
+#endif
+
+// The more the ratio is far from 1, the more we accept a big error.
+float epsilon_tweak_ratio(float ratio)
+{
+ return ratio >= 1 ? ratio : 1 / ratio;
+}
+
+// Epsilon values for comparing resampled data to expected data.
+// The bigger the resampling ratio is, the more lax we are about errors.
+template<typename T>
+T epsilon(float ratio);
+
+template<>
+float epsilon(float ratio) {
+ return 0.08f * epsilon_tweak_ratio(ratio);
+}
+
+template<>
+int16_t epsilon(float ratio) {
+ return static_cast<int16_t>(10 * epsilon_tweak_ratio(ratio));
+}
+
+void test_delay_lines(uint32_t delay_frames, uint32_t channels, uint32_t chunk_ms)
+{
+ const size_t length_s = 2;
+ const size_t rate = 44100;
+ const size_t length_frames = rate * length_s;
+ delay_line<float> delay(delay_frames, channels, rate);
+ auto_array<float> input;
+ auto_array<float> output;
+ uint32_t chunk_length = channels * chunk_ms * rate / 1000;
+ uint32_t output_offset = 0;
+ uint32_t channel = 0;
+
+ /** Generate diracs every 100 frames, and check they are delayed. */
+ input.push_silence(length_frames * channels);
+ for (uint32_t i = 0; i < input.length() - 1; i+=100) {
+ input.data()[i + channel] = 0.5;
+ channel = (channel + 1) % channels;
+ }
+ dump("input.raw", input.data(), input.length());
+ while(input.length()) {
+ uint32_t to_pop = std::min<uint32_t>(input.length(), chunk_length * channels);
+ float * in = delay.input_buffer(to_pop / channels);
+ input.pop(in, to_pop);
+ delay.written(to_pop / channels);
+ output.push_silence(to_pop);
+ delay.output(output.data() + output_offset, to_pop / channels);
+ output_offset += to_pop;
+ }
+
+ // Check the diracs have been shifted by `delay_frames` frames.
+ for (uint32_t i = 0; i < output.length() - delay_frames * channels + 1; i+=100) {
+ ASSERT_EQ(output.data()[i + channel + delay_frames * channels], 0.5);
+ channel = (channel + 1) % channels;
+ }
+
+ dump("output.raw", output.data(), output.length());
+}
+/**
+ * This takes sine waves with a certain `channels` count, `source_rate`, and
+ * resample them, by chunk of `chunk_duration` milliseconds, to `target_rate`.
+ * Then a sample-wise comparison is performed against a sine wave generated at
+ * the correct rate.
+ */
+template<typename T>
+void test_resampler_one_way(uint32_t channels, uint32_t source_rate, uint32_t target_rate, float chunk_duration)
+{
+ size_t chunk_duration_in_source_frames = static_cast<uint32_t>(ceil(chunk_duration * source_rate / 1000.));
+ float resampling_ratio = static_cast<float>(source_rate) / target_rate;
+ cubeb_resampler_speex_one_way<T> resampler(channels, source_rate, target_rate, 3);
+ auto_array<T> source(channels * source_rate * 10);
+ auto_array<T> destination(channels * target_rate * 10);
+ auto_array<T> expected(channels * target_rate * 10);
+ uint32_t phase_index = 0;
+ uint32_t offset = 0;
+ const uint32_t buf_len = 2; /* seconds */
+
+ // generate a sine wave in each channel, at the source sample rate
+ source.push_silence(channels * source_rate * buf_len);
+ while(offset != source.length()) {
+ float p = phase_index++ / static_cast<float>(source_rate);
+ for (uint32_t j = 0; j < channels; j++) {
+ source.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
+ }
+ }
+
+ dump("input.raw", source.data(), source.length());
+
+ expected.push_silence(channels * target_rate * buf_len);
+ // generate a sine wave in each channel, at the target sample rate.
+ // Insert silent samples at the beginning to account for the resampler latency.
+ offset = resampler.latency() * channels;
+ for (uint32_t i = 0; i < offset; i++) {
+ expected.data()[i] = 0.0f;
+ }
+ phase_index = 0;
+ while (offset != expected.length()) {
+ float p = phase_index++ / static_cast<float>(target_rate);
+ for (uint32_t j = 0; j < channels; j++) {
+ expected.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
+ }
+ }
+
+ dump("expected.raw", expected.data(), expected.length());
+
+ // resample by chunk
+ uint32_t write_offset = 0;
+ destination.push_silence(channels * target_rate * buf_len);
+ while (write_offset < destination.length())
+ {
+ size_t output_frames = static_cast<uint32_t>(floor(chunk_duration_in_source_frames / resampling_ratio));
+ uint32_t input_frames = resampler.input_needed_for_output(output_frames);
+ resampler.input(source.data(), input_frames);
+ source.pop(nullptr, input_frames * channels);
+ resampler.output(destination.data() + write_offset,
+ std::min(output_frames, (destination.length() - write_offset) / channels));
+ write_offset += output_frames * channels;
+ }
+
+ dump("output.raw", destination.data(), expected.length());
+
+ // compare, taking the latency into account
+ bool fuzzy_equal = true;
+ for (uint32_t i = resampler.latency() + 1; i < expected.length(); i++) {
+ float diff = fabs(expected.data()[i] - destination.data()[i]);
+ if (diff > epsilon<T>(resampling_ratio)) {
+ fprintf(stderr, "divergence at %d: %f %f (delta %f)\n", i, expected.data()[i], destination.data()[i], diff);
+ fuzzy_equal = false;
+ }
+ }
+ ASSERT_TRUE(fuzzy_equal);
+}
+
+template<typename T>
+cubeb_sample_format cubeb_format();
+
+template<>
+cubeb_sample_format cubeb_format<float>()
+{
+ return CUBEB_SAMPLE_FLOAT32NE;
+}
+
+template<>
+cubeb_sample_format cubeb_format<short>()
+{
+ return CUBEB_SAMPLE_S16NE;
+}
+
+struct osc_state {
+ osc_state()
+ : input_phase_index(0)
+ , output_phase_index(0)
+ , output_offset(0)
+ , input_channels(0)
+ , output_channels(0)
+ {}
+ uint32_t input_phase_index;
+ uint32_t max_output_phase_index;
+ uint32_t output_phase_index;
+ uint32_t output_offset;
+ uint32_t input_channels;
+ uint32_t output_channels;
+ uint32_t output_rate;
+ uint32_t target_rate;
+ auto_array<float> input;
+ auto_array<float> output;
+};
+
+uint32_t fill_with_sine(float * buf, uint32_t rate, uint32_t channels,
+ uint32_t frames, uint32_t initial_phase)
+{
+ uint32_t offset = 0;
+ for (uint32_t i = 0; i < frames; i++) {
+ float p = initial_phase++ / static_cast<float>(rate);
+ for (uint32_t j = 0; j < channels; j++) {
+ buf[offset++] = 0.5 * sin(440. * 2 * PI * p);
+ }
+ }
+ return initial_phase;
+}
+
+long data_cb_resampler(cubeb_stream * /*stm*/, void * user_ptr,
+ const void * input_buffer, void * output_buffer, long frame_count)
+{
+ osc_state * state = reinterpret_cast<osc_state*>(user_ptr);
+ const float * in = reinterpret_cast<const float*>(input_buffer);
+ float * out = reinterpret_cast<float*>(output_buffer);
+
+ state->input.push(in, frame_count * state->input_channels);
+
+ /* Check how much output frames we need to write */
+ uint32_t remaining = state->max_output_phase_index - state->output_phase_index;
+ uint32_t to_write = std::min<uint32_t>(remaining, frame_count);
+ state->output_phase_index = fill_with_sine(out,
+ state->target_rate,
+ state->output_channels,
+ to_write,
+ state->output_phase_index);
+
+ return to_write;
+}
+
+template<typename T>
+bool array_fuzzy_equal(const auto_array<T>& lhs, const auto_array<T>& rhs, T epsi)
+{
+ uint32_t len = std::min(lhs.length(), rhs.length());
+
+ for (uint32_t i = 0; i < len; i++) {
+ if (fabs(lhs.at(i) - rhs.at(i)) > epsi) {
+ std::cout << "not fuzzy equal at index: " << i
+ << " lhs: " << lhs.at(i) << " rhs: " << rhs.at(i)
+ << " delta: " << fabs(lhs.at(i) - rhs.at(i))
+ << " epsilon: "<< epsi << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+template<typename T>
+void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels,
+ uint32_t input_rate, uint32_t output_rate,
+ uint32_t target_rate, float chunk_duration)
+{
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ osc_state state;
+
+ input_params.format = output_params.format = cubeb_format<T>();
+ state.input_channels = input_params.channels = input_channels;
+ state.output_channels = output_params.channels = output_channels;
+ input_params.rate = input_rate;
+ state.output_rate = output_params.rate = output_rate;
+ state.target_rate = target_rate;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+ long got;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate,
+ data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ long latency = cubeb_resampler_latency(resampler);
+
+ const uint32_t duration_s = 2;
+ int32_t duration_frames = duration_s * target_rate;
+ uint32_t input_array_frame_count = ceil(chunk_duration * input_rate / 1000) + ceilf(static_cast<float>(input_rate) / target_rate) * 2;
+ uint32_t output_array_frame_count = chunk_duration * output_rate / 1000;
+ auto_array<float> input_buffer(input_channels * input_array_frame_count);
+ auto_array<float> output_buffer(output_channels * output_array_frame_count);
+ auto_array<float> expected_resampled_input(input_channels * duration_frames);
+ auto_array<float> expected_resampled_output(output_channels * output_rate * duration_s);
+
+ state.max_output_phase_index = duration_s * target_rate;
+
+ expected_resampled_input.push_silence(input_channels * duration_frames);
+ expected_resampled_output.push_silence(output_channels * output_rate * duration_s);
+
+ /* expected output is a 440Hz sine wave at 16kHz */
+ fill_with_sine(expected_resampled_input.data() + latency,
+ target_rate, input_channels, duration_frames - latency, 0);
+ /* expected output is a 440Hz sine wave at 32kHz */
+ fill_with_sine(expected_resampled_output.data() + latency,
+ output_rate, output_channels, output_rate * duration_s - latency, 0);
+
+ while (state.output_phase_index != state.max_output_phase_index) {
+ uint32_t leftover_samples = input_buffer.length() * input_channels;
+ input_buffer.reserve(input_array_frame_count);
+ state.input_phase_index = fill_with_sine(input_buffer.data() + leftover_samples,
+ input_rate,
+ input_channels,
+ input_array_frame_count - leftover_samples,
+ state.input_phase_index);
+ long input_consumed = input_array_frame_count;
+ input_buffer.set_length(input_array_frame_count);
+
+ got = cubeb_resampler_fill(resampler,
+ input_buffer.data(), &input_consumed,
+ output_buffer.data(), output_array_frame_count);
+
+ /* handle leftover input */
+ if (input_array_frame_count != static_cast<uint32_t>(input_consumed)) {
+ input_buffer.pop(nullptr, input_consumed * input_channels);
+ } else {
+ input_buffer.clear();
+ }
+
+ state.output.push(output_buffer.data(), got * state.output_channels);
+ }
+
+ dump("input_expected.raw", expected_resampled_input.data(), expected_resampled_input.length());
+ dump("output_expected.raw", expected_resampled_output.data(), expected_resampled_output.length());
+ dump("input.raw", state.input.data(), state.input.length());
+ dump("output.raw", state.output.data(), state.output.length());
+
+ // This is disabled because the latency estimation in the resampler code is
+ // slightly off so we can generate expected vectors.
+ // See https://github.com/kinetiknz/cubeb/issues/93
+ // ASSERT_TRUE(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
+ // ASSERT_TRUE(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate)));
+
+ cubeb_resampler_destroy(resampler);
+}
+
+#define array_size(x) (sizeof(x) / sizeof(x[0]))
+
+TEST(cubeb, resampler_one_way)
+{
+ /* Test one way resamplers */
+ for (uint32_t channels = 1; channels <= max_channels; channels++) {
+ for (uint32_t source_rate = 0; source_rate < array_size(sample_rates); source_rate++) {
+ for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
+ for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
+ fprintf(stderr, "one_way: channels: %d, source_rate: %d, dest_rate: %d, chunk_duration: %d\n",
+ channels, sample_rates[source_rate], sample_rates[dest_rate], chunk_duration);
+ test_resampler_one_way<float>(channels, sample_rates[source_rate],
+ sample_rates[dest_rate], chunk_duration);
+ }
+ }
+ }
+ }
+}
+
+TEST(cubeb, DISABLED_resampler_duplex)
+{
+ for (uint32_t input_channels = 1; input_channels <= max_channels; input_channels++) {
+ for (uint32_t output_channels = 1; output_channels <= max_channels; output_channels++) {
+ for (uint32_t source_rate_input = 0; source_rate_input < array_size(sample_rates); source_rate_input++) {
+ for (uint32_t source_rate_output = 0; source_rate_output < array_size(sample_rates); source_rate_output++) {
+ for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
+ for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
+ fprintf(stderr, "input channels:%d output_channels:%d input_rate:%d "
+ "output_rate:%d target_rate:%d chunk_ms:%d\n",
+ input_channels, output_channels,
+ sample_rates[source_rate_input],
+ sample_rates[source_rate_output],
+ sample_rates[dest_rate],
+ chunk_duration);
+ test_resampler_duplex<float>(input_channels, output_channels,
+ sample_rates[source_rate_input],
+ sample_rates[source_rate_output],
+ sample_rates[dest_rate],
+ chunk_duration);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+TEST(cubeb, resampler_delay_line)
+{
+ for (uint32_t channel = 1; channel <= 2; channel++) {
+ for (uint32_t delay_frames = 4; delay_frames <= 40; delay_frames+=chunk_increment) {
+ for (uint32_t chunk_size = 10; chunk_size <= 30; chunk_size++) {
+ fprintf(stderr, "channel: %d, delay_frames: %d, chunk_size: %d\n",
+ channel, delay_frames, chunk_size);
+ test_delay_lines(delay_frames, channel, chunk_size);
+ }
+ }
+ }
+}
+
+long test_output_only_noop_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ EXPECT_TRUE(output_buffer);
+ EXPECT_TRUE(!input_buffer);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_output_only_noop)
+{
+ cubeb_stream_params output_params;
+ int target_rate;
+
+ output_params.rate = 44100;
+ output_params.channels = 1;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ target_rate = output_params.rate;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
+ test_output_only_noop_data_cb, nullptr,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ const long out_frames = 128;
+ float out_buffer[out_frames];
+ long got;
+
+ got = cubeb_resampler_fill(resampler, nullptr, nullptr,
+ out_buffer, out_frames);
+
+ ASSERT_EQ(got, out_frames);
+
+ cubeb_resampler_destroy(resampler);
+}
+
+long test_drain_data_cb(cubeb_stream * /*stm*/, void * user_ptr,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ EXPECT_TRUE(output_buffer);
+ EXPECT_TRUE(!input_buffer);
+ auto cb_count = static_cast<int *>(user_ptr);
+ (*cb_count)++;
+ return frame_count - 1;
+}
+
+TEST(cubeb, resampler_drain)
+{
+ cubeb_stream_params output_params;
+ int target_rate;
+
+ output_params.rate = 44100;
+ output_params.channels = 1;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ target_rate = 48000;
+ int cb_count = 0;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
+ test_drain_data_cb, &cb_count,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ const long out_frames = 128;
+ float out_buffer[out_frames];
+ long got;
+
+ do {
+ got = cubeb_resampler_fill(resampler, nullptr, nullptr,
+ out_buffer, out_frames);
+ } while (got == out_frames);
+
+ /* The callback should be called once but not again after returning <
+ * frame_count. */
+ ASSERT_EQ(cb_count, 1);
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+void check_output(const void * input_buffer, void * output_buffer, long frame_count)
+{
+ ASSERT_EQ(input_buffer, nullptr);
+ ASSERT_EQ(frame_count, 256);
+ ASSERT_TRUE(!!output_buffer);
+}
+
+long cb_passthrough_resampler_output(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ check_output(input_buffer, output_buffer, frame_count);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_passthrough_output_only)
+{
+ // Test that the passthrough resampler works when there is only an output stream.
+ cubeb_stream_params output_params;
+
+ const size_t output_channels = 2;
+ output_params.channels = output_channels;
+ output_params.rate = 44100;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ int target_rate = output_params.rate;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params,
+ target_rate, cb_passthrough_resampler_output, nullptr,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ float output_buffer[output_channels * 256];
+
+ long got;
+ for (uint32_t i = 0; i < 30; i++) {
+ got = cubeb_resampler_fill(resampler, nullptr, nullptr, output_buffer, 256);
+ ASSERT_EQ(got, 256);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+void check_input(const void * input_buffer, void * output_buffer, long frame_count)
+{
+ ASSERT_EQ(output_buffer, nullptr);
+ ASSERT_EQ(frame_count, 256);
+ ASSERT_TRUE(!!input_buffer);
+}
+
+long cb_passthrough_resampler_input(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ check_input(input_buffer, output_buffer, frame_count);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_passthrough_input_only)
+{
+ // Test that the passthrough resampler works when there is only an output stream.
+ cubeb_stream_params input_params;
+
+ const size_t input_channels = 2;
+ input_params.channels = input_channels;
+ input_params.rate = 44100;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ int target_rate = input_params.rate;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, nullptr,
+ target_rate, cb_passthrough_resampler_input, nullptr,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ float input_buffer[input_channels * 256];
+
+ long got;
+ for (uint32_t i = 0; i < 30; i++) {
+ long int frames = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer, &frames, nullptr, 0);
+ ASSERT_EQ(got, 256);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+template<typename T>
+long seq(T* array, int stride, long start, long count)
+{
+ uint32_t output_idx = 0;
+ for(int i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ array[output_idx + j] = static_cast<T>(start + i);
+ }
+ output_idx += stride;
+ }
+ return start + count;
+}
+
+template<typename T>
+void is_seq(T * array, int stride, long count, long expected_start)
+{
+ uint32_t output_index = 0;
+ for (long i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ ASSERT_EQ(array[output_index + j], expected_start + i);
+ }
+ output_index += stride;
+ }
+}
+
+template<typename T>
+void is_not_seq(T * array, int stride, long count, long expected_start)
+{
+ uint32_t output_index = 0;
+ for (long i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ ASSERT_NE(array[output_index + j], expected_start + i);
+ }
+ output_index += stride;
+ }
+}
+
+struct closure {
+ int input_channel_count;
+};
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+template<typename T>
+void check_duplex(const T * input_buffer,
+ T * output_buffer, long frame_count,
+ int input_channel_count)
+{
+ ASSERT_EQ(frame_count, 256);
+ // Silence scan-build warning.
+ ASSERT_TRUE(!!output_buffer); assert(output_buffer);
+ ASSERT_TRUE(!!input_buffer); assert(input_buffer);
+
+ int output_index = 0;
+ int input_index = 0;
+ for (int i = 0; i < frame_count; i++) {
+ // output is two channels, input one or two channels.
+ if (input_channel_count == 1) {
+ output_buffer[output_index] = output_buffer[output_index + 1] = input_buffer[i];
+ } else if (input_channel_count == 2) {
+ output_buffer[output_index] = input_buffer[input_index];
+ output_buffer[output_index + 1] = input_buffer[input_index + 1];
+ }
+ output_index += 2;
+ input_index += input_channel_count;
+ }
+}
+
+long cb_passthrough_resampler_duplex(cubeb_stream * /*stm*/, void * user_ptr,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ closure * c = reinterpret_cast<closure*>(user_ptr);
+ check_duplex<float>(static_cast<const float*>(input_buffer),
+ static_cast<float*>(output_buffer),
+ frame_count, c->input_channel_count);
+ return frame_count;
+}
+
+
+TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
+{
+ // Test that when pre-buffering on resampler creation, we can survive an input
+ // callback being delayed.
+
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ const int input_channels = 1;
+ const int output_channels = 2;
+
+ input_params.channels = input_channels;
+ input_params.rate = 44100;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ output_params.channels = output_channels;
+ output_params.rate = input_params.rate;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ int target_rate = input_params.rate;
+
+ closure c;
+ c.input_channel_count = input_channels;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
+ target_rate, cb_passthrough_resampler_duplex, &c,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ const long BUF_BASE_SIZE = 256;
+ float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2];
+ float input_buffer_glitch[input_channels * BUF_BASE_SIZE * 2];
+ float input_buffer_normal[input_channels * BUF_BASE_SIZE];
+ float output_buffer[output_channels * BUF_BASE_SIZE];
+
+ long seq_idx = 0;
+ long output_seq_idx = 0;
+
+ long prebuffer_frames = ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels;
+ seq_idx = seq(input_buffer_prebuffer, input_channels, seq_idx,
+ prebuffer_frames);
+
+ long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer, &prebuffer_frames,
+ output_buffer, BUF_BASE_SIZE);
+
+ output_seq_idx += BUF_BASE_SIZE;
+
+ // prebuffer_frames will hold the frames used by the resampler.
+ ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+
+ for (uint32_t i = 0; i < 300; i++) {
+ long int frames = BUF_BASE_SIZE;
+ // Simulate that sometimes, we don't have the input callback on time
+ if (i != 0 && (i % 100) == 0) {
+ long zero = 0;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal /* unused here */,
+ &zero, output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ } else if (i != 0 && (i % 100) == 1) {
+ // if this is the case, the on the next iteration, we'll have twice the
+ // amount of input frames
+ seq_idx = seq(input_buffer_glitch, input_channels, seq_idx, BUF_BASE_SIZE * 2);
+ frames = 2 * BUF_BASE_SIZE;
+ got = cubeb_resampler_fill(resampler, input_buffer_glitch, &frames, output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ } else {
+ // normal case
+ seq_idx = seq(input_buffer_normal, input_channels, seq_idx, BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal, &normal_input_frame_count, output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// Artificially simulate output thread underruns,
+// by building up artificial delay in the input.
+// Check that the frame drop logic kicks in.
+TEST(cubeb, resampler_drift_drop_data)
+{
+ for (uint32_t input_channels = 1; input_channels < 3; input_channels++) {
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ const int output_channels = 2;
+ const int sample_rate = 44100;
+
+ input_params.channels = input_channels;
+ input_params.rate = sample_rate;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ output_params.channels = output_channels;
+ output_params.rate = sample_rate;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ int target_rate = input_params.rate;
+
+ closure c;
+ c.input_channel_count = input_channels;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
+ target_rate, cb_passthrough_resampler_duplex, &c,
+ CUBEB_RESAMPLER_QUALITY_VOIP);
+
+ const long BUF_BASE_SIZE = 256;
+
+ // The factor by which the deadline is missed. This is intentionally
+ // kind of large to trigger the frame drop quickly. In real life, multiple
+ // smaller under-runs would accumulate.
+ const long UNDERRUN_FACTOR = 10;
+ // Number buffer used for pre-buffering, that some backends do.
+ const long PREBUFFER_FACTOR = 2;
+
+ std::vector<float> input_buffer_prebuffer(input_channels * BUF_BASE_SIZE * PREBUFFER_FACTOR);
+ std::vector<float> input_buffer_glitch(input_channels * BUF_BASE_SIZE * UNDERRUN_FACTOR);
+ std::vector<float> input_buffer_normal(input_channels * BUF_BASE_SIZE);
+ std::vector<float> output_buffer(output_channels * BUF_BASE_SIZE);
+
+ long seq_idx = 0;
+ long output_seq_idx = 0;
+
+ long prebuffer_frames = input_buffer_prebuffer.size() / input_params.channels;
+ seq_idx = seq(input_buffer_prebuffer.data(), input_channels, seq_idx,
+ prebuffer_frames);
+
+ long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer.data(), &prebuffer_frames,
+ output_buffer.data(), BUF_BASE_SIZE);
+
+ output_seq_idx += BUF_BASE_SIZE;
+
+ // prebuffer_frames will hold the frames used by the resampler.
+ ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+
+ for (uint32_t i = 0; i < 300; i++) {
+ long int frames = BUF_BASE_SIZE;
+ if (i != 0 && (i % 100) == 1) {
+ // Once in a while, the output thread misses its deadline.
+ // The input thread still produces data, so it ends up accumulating. Simulate this by providing a
+ // much bigger input buffer. Check that the sequence is now unaligned, meaning we've dropped data
+ // to keep everything in sync.
+ seq_idx = seq(input_buffer_glitch.data(), input_channels, seq_idx, BUF_BASE_SIZE * UNDERRUN_FACTOR);
+ frames = BUF_BASE_SIZE * UNDERRUN_FACTOR;
+ got = cubeb_resampler_fill(resampler, input_buffer_glitch.data(), &frames, output_buffer.data(), BUF_BASE_SIZE);
+ is_seq(output_buffer.data(), 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ else if (i != 0 && (i % 100) == 2) {
+ // On the next iteration, the sequence should be broken
+ seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
+ is_not_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
+ // Reclock so that we can use is_seq again.
+ output_seq_idx = output_buffer[BUF_BASE_SIZE * output_channels - 1] + 1;
+ }
+ else {
+ // normal case
+ seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
+ is_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+ }
+
+ cubeb_resampler_destroy(resampler);
+ }
+}
+
+static long
+passthrough_resampler_fill_eq_input(cubeb_stream * stream,
+ void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer,
+ long nframes) {
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ }();
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_eq_input) {
+ uint32_t channels = 2;
+ uint32_t sample_rate = 44100;
+ passthrough_resampler<float> resampler =
+ passthrough_resampler<float>(nullptr, passthrough_resampler_fill_eq_input,
+ nullptr, channels, sample_rate);
+
+ long input_frame_count = 32;
+ long output_frame_count = 32;
+ float input[64] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+ long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+}
+
+static long
+passthrough_resampler_fill_short_input(cubeb_stream * stream,
+ void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer,
+ long nframes) {
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ // First part contains the input
+ for (int i = 0; i < 32; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ // missing part contains silence
+ for (int i = 32; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.0);
+ }
+ }();
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_short_input) {
+ uint32_t channels = 2;
+ uint32_t sample_rate = 44100;
+ passthrough_resampler<float> resampler =
+ passthrough_resampler<float>(nullptr, passthrough_resampler_fill_short_input,
+ nullptr, channels, sample_rate);
+
+ long input_frame_count = 16;
+ long output_frame_count = 32;
+ float input[64] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+ long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used are less than the output frames due to glitch.
+ ASSERT_EQ(input_frame_count, output_frame_count - 16);
+}
+
+static long
+passthrough_resampler_fill_input_left(cubeb_stream * stream,
+ void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer,
+ long nframes) {
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ int iteration = *static_cast<int*>(user_ptr);
+ if (iteration == 1) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ }();
+ } else if (iteration == 2) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 32; ++i) {
+ // First part contains the reamaining input samples from previous
+ // iteration (since they were more).
+ ASSERT_FLOAT_EQ(input[i], 0.01 * (i + 64));
+ // next part contains the new buffer
+ ASSERT_FLOAT_EQ(input[i + 32], 0.01 * i);
+ }
+ }();
+ } else if (iteration == 3) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 32; ++i) {
+ // First part (16 frames) contains the reamaining input samples
+ // from previous iteration (since they were more).
+ ASSERT_FLOAT_EQ(input[i], 0.01 * (i + 32));
+ }
+ for (int i = 0; i < 16; ++i) {
+ // next part (8 frames) contains the new input buffer.
+ ASSERT_FLOAT_EQ(input[i + 32], 0.01 * i);
+ // last part (8 frames) contains silence.
+ ASSERT_FLOAT_EQ(input[i + 32 + 16], 0.0);
+ }
+ }();
+ }
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_input_left) {
+ const uint32_t channels = 2;
+ const uint32_t sample_rate = 44100;
+ int iteration = 0;
+ passthrough_resampler<float> resampler =
+ passthrough_resampler<float>(nullptr, passthrough_resampler_fill_input_left,
+ &iteration, channels, sample_rate);
+
+ long input_frame_count = 48; // 32 + 16
+ const long output_frame_count = 32;
+ float input[96] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+
+ // 1st iteration, add the extra input.
+ iteration = 1;
+ long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+
+ // 2st iteration, use the extra input from previous iteration,
+ // 16 frames are remaining in the input buffer.
+ input_frame_count = 32; // we need 16 input frames but we get more;
+ iteration = 2;
+ got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+
+ // 3rd iteration, use the extra input from previous iteration.
+ // 16 frames are remaining in the input buffer.
+ input_frame_count = 16 - 8; // We need 16 more input frames but we only get 8.
+ iteration = 3;
+ got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used are less than the output frames due to glitch.
+ ASSERT_EQ(input_frame_count, output_frame_count - 8);
+}
+
+TEST(cubeb, individual_methods) {
+ const uint32_t channels = 2;
+ const uint32_t sample_rate = 44100;
+ const uint32_t frames = 256;
+
+ delay_line<float> dl(10, channels, sample_rate);
+ uint32_t frames_needed1 = dl.input_needed_for_output(0);
+ ASSERT_EQ(frames_needed1, 0u);
+
+ cubeb_resampler_speex_one_way<float> one_way(channels, sample_rate, sample_rate, CUBEB_RESAMPLER_QUALITY_DEFAULT);
+ float buffer[channels * frames] = {0.0};
+ // Add all frames in the resampler's internal buffer.
+ one_way.input(buffer, frames);
+ // Ask for less than the existing frames, this would create a uint overlflow without the fix.
+ uint32_t frames_needed2 = one_way.input_needed_for_output(0);
+ ASSERT_EQ(frames_needed2, 0u);
+}
+
diff --git a/media/libcubeb/gtest/test_ring_array.cpp b/media/libcubeb/gtest/test_ring_array.cpp
new file mode 100644
index 0000000000..d258d50dbe
--- /dev/null
+++ b/media/libcubeb/gtest/test_ring_array.cpp
@@ -0,0 +1,73 @@
+#include "gtest/gtest.h"
+#ifdef __APPLE__
+#include <string.h>
+#include <iostream>
+#include <CoreAudio/CoreAudioTypes.h>
+#include "cubeb/cubeb.h"
+#include "cubeb_ring_array.h"
+
+TEST(cubeb, ring_array)
+{
+ ring_array ra;
+
+ ASSERT_EQ(ring_array_init(&ra, 0, 0, 1, 1), CUBEB_ERROR_INVALID_PARAMETER);
+ ASSERT_EQ(ring_array_init(&ra, 1, 0, 0, 1), CUBEB_ERROR_INVALID_PARAMETER);
+
+ unsigned int capacity = 8;
+ ring_array_init(&ra, capacity, sizeof(int), 1, 1);
+ int verify_data[capacity] ;// {1,2,3,4,5,6,7,8};
+ AudioBuffer * p_data = NULL;
+
+ for (unsigned int i = 0; i < capacity; ++i) {
+ verify_data[i] = i; // in case capacity change value
+ *(int*)ra.buffer_array[i].mData = i;
+ ASSERT_EQ(ra.buffer_array[i].mDataByteSize, sizeof(int));
+ ASSERT_EQ(ra.buffer_array[i].mNumberChannels, 1u);
+ }
+
+ /* Get store buffers*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*(int*)p_data->mData, verify_data[i]);
+ }
+ /*Now array is full extra store should give NULL*/
+ ASSERT_EQ(ring_array_get_free_buffer(&ra), nullptr);
+ /* Get fetch buffers*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_data_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*(int*)p_data->mData, verify_data[i]);
+ }
+ /*Now array is empty extra fetch should give NULL*/
+ ASSERT_EQ(ring_array_get_data_buffer(&ra), nullptr);
+
+ p_data = NULL;
+ /* Repeated store fetch should can go for ever*/
+ for (unsigned int i = 0; i < 2*capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(ring_array_get_data_buffer(&ra), p_data);
+ }
+
+ p_data = NULL;
+ /* Verify/modify buffer data*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*((int*)p_data->mData), verify_data[i]);
+ (*((int*)p_data->mData))++; // Modify data
+ }
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_data_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*((int*)p_data->mData), verify_data[i]+1); // Verify modified data
+ }
+
+ ring_array_destroy(&ra);
+}
+#else
+TEST(cubeb, DISABLED_ring_array)
+{
+}
+#endif
diff --git a/media/libcubeb/tests/test_sanity.cpp b/media/libcubeb/gtest/test_sanity.cpp
index 77973ff150..5fc72f5356 100644
--- a/media/libcubeb/tests/test_sanity.cpp
+++ b/media/libcubeb/gtest/test_sanity.cpp
@@ -4,52 +4,63 @@
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
+#endif
#include "cubeb/cubeb.h"
-#include <assert.h>
+#include <atomic>
#include <stdio.h>
#include <string.h>
#include <math.h>
-#include "common.h"
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
-#define BEGIN_TEST fprintf(stderr, "START %s\n", __func__)
-#define END_TEST fprintf(stderr, "END %s\n", __func__)
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
#define STREAM_RATE 44100
#define STREAM_LATENCY 100 * STREAM_RATE / 1000
#define STREAM_CHANNELS 1
-#if (defined(_WIN32) || defined(__WIN32__))
-#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
-#else
+#define STREAM_LAYOUT CUBEB_LAYOUT_MONO
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
-#endif
-template<typename T, size_t N>
-constexpr size_t
-ARRAY_LENGTH(T(&)[N])
+int is_windows_7()
{
- return N;
+#ifdef __MINGW32__
+ fprintf(stderr, "Warning: this test was built with MinGW.\n"
+ "MinGW does not contain necessary version checking infrastructure. Claiming to be Windows 7, even if we're not.\n");
+ return 1;
+#endif
+#if (defined(_WIN32) || defined(__WIN32__)) && ( !defined(__MINGW32__))
+ OSVERSIONINFOEX osvi;
+ DWORDLONG condition_mask = 0;
+
+ ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+
+ // NT 6.1 is Windows 7
+ osvi.dwMajorVersion = 6;
+ osvi.dwMinorVersion = 1;
+
+ VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_EQUAL);
+ VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
+
+ return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, condition_mask);
+#else
+ return 0;
+#endif
}
static int dummy;
-static uint64_t total_frames_written;
+static std::atomic<uint64_t> total_frames_written;
static int delay_callback;
static long
test_data_callback(cubeb_stream * stm, void * user_ptr, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
{
- assert(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
-#if (defined(_WIN32) || defined(__WIN32__))
- memset(outputbuffer, 0, nframes * sizeof(float));
-#else
+ EXPECT_TRUE(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
+ assert(outputbuffer);
memset(outputbuffer, 0, nframes * sizeof(short));
-#endif
total_frames_written += nframes;
if (delay_callback) {
@@ -63,121 +74,103 @@ test_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state /*s
{
}
-static void
-test_init_destroy_context(void)
+TEST(cubeb, init_destroy_context)
{
int r;
cubeb * ctx;
char const* backend_id;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
-
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
backend_id = cubeb_get_backend_id(ctx);
- assert(backend_id);
+ ASSERT_TRUE(backend_id);
fprintf(stderr, "Backend: %s\n", backend_id);
cubeb_destroy(ctx);
-
- END_TEST;
}
-static void
-test_init_destroy_multiple_contexts(void)
+TEST(cubeb, init_destroy_multiple_contexts)
{
size_t i;
int r;
cubeb * ctx[4];
int order[4] = {2, 0, 3, 1};
- assert(ARRAY_LENGTH(ctx) == ARRAY_LENGTH(order));
-
- BEGIN_TEST;
+ ASSERT_EQ(ARRAY_LENGTH(ctx), ARRAY_LENGTH(order));
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
- r = cubeb_init(&ctx[i], NULL);
- assert(r == 0 && ctx[i]);
+ r = common_init(&ctx[i], NULL);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx[i], nullptr);
}
/* destroy in a different order */
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
cubeb_destroy(ctx[order[i]]);
}
-
- END_TEST;
}
-static void
-test_context_variables(void)
+TEST(cubeb, context_variables)
{
int r;
cubeb * ctx;
uint32_t value;
cubeb_stream_params params;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_context_variables");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_context_variables");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.channels = STREAM_CHANNELS;
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
- r = cubeb_get_min_latency(ctx, params, &value);
- assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &params, &value);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
- assert(value > 0);
+ ASSERT_TRUE(value > 0);
}
r = cubeb_get_preferred_sample_rate(ctx, &value);
- assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
- assert(value > 0);
+ ASSERT_TRUE(value > 0);
}
cubeb_destroy(ctx);
-
- END_TEST;
}
-static void
-test_init_destroy_stream(void)
+TEST(cubeb, init_destroy_stream)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0 && stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
-
- END_TEST;
}
-static void
-test_init_destroy_multiple_streams(void)
+TEST(cubeb, init_destroy_multiple_streams)
{
size_t i;
int r;
@@ -185,23 +178,21 @@ test_init_destroy_multiple_streams(void)
cubeb_stream * stream[8];
cubeb_stream_params params;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0);
- assert(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i], nullptr);
}
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
@@ -209,43 +200,72 @@ test_init_destroy_multiple_streams(void)
}
cubeb_destroy(ctx);
-
- END_TEST;
}
-static void
-test_configure_stream(void)
+TEST(cubeb, configure_stream)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
- params.channels = 2; // panning
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0 && stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
r = cubeb_stream_set_volume(stream, 1.0f);
- assert(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+ ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
- r = cubeb_stream_set_panning(stream, 0.0f);
- assert(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+ r = cubeb_stream_set_name(stream, "test 2");
+ ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, configure_stream_undefined_layout)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ delay(100);
+
+ r = cubeb_stream_stop(stream);
+ ASSERT_EQ(r, CUBEB_OK);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
- END_TEST;
}
static void
@@ -257,34 +277,31 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
cubeb_stream * stream[8];
cubeb_stream_params params;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0);
- assert(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i], nullptr);
if (early) {
r = cubeb_stream_start(stream[i]);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
}
}
-
if (!early) {
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_start(stream[i]);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
}
}
@@ -295,25 +312,43 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
if (!early) {
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_stop(stream[i]);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
}
}
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
if (early) {
r = cubeb_stream_stop(stream[i]);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
}
cubeb_stream_destroy(stream[i]);
}
cubeb_destroy(ctx);
+}
- END_TEST;
+TEST(cubeb, init_start_stop_destroy_multiple_streams)
+{
+ /* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and
+ * calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is
+ * the HRESULT value for "Cannot create a file when that file already exists",
+ * and is not documented as a possible return value for this call. Hence, we
+ * try to limit the number of streams we create in this test. */
+ if (!is_windows_7()) {
+ delay_callback = 0;
+ test_init_start_stop_destroy_multiple_streams(0, 0);
+ test_init_start_stop_destroy_multiple_streams(1, 0);
+ test_init_start_stop_destroy_multiple_streams(0, 150);
+ test_init_start_stop_destroy_multiple_streams(1, 150);
+ delay_callback = 1;
+ test_init_start_stop_destroy_multiple_streams(0, 0);
+ test_init_start_stop_destroy_multiple_streams(1, 0);
+ test_init_start_stop_destroy_multiple_streams(0, 150);
+ test_init_start_stop_destroy_multiple_streams(1, 150);
+ }
}
-static void
-test_init_destroy_multiple_contexts_and_streams(void)
+TEST(cubeb, init_destroy_multiple_contexts_and_streams)
{
size_t i, j;
int r;
@@ -321,26 +356,32 @@ test_init_destroy_multiple_contexts_and_streams(void)
cubeb_stream * stream[8];
cubeb_stream_params params;
size_t streams_per_ctx = ARRAY_LENGTH(stream) / ARRAY_LENGTH(ctx);
- assert(ARRAY_LENGTH(ctx) * streams_per_ctx == ARRAY_LENGTH(stream));
+ ASSERT_EQ(ARRAY_LENGTH(ctx) * streams_per_ctx, ARRAY_LENGTH(stream));
- BEGIN_TEST;
+ /* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and
+ * calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is
+ * the HRESULT value for "Cannot create a file when that file already exists",
+ * and is not documented as a possible return value for this call. Hence, we
+ * try to limit the number of streams we create in this test. */
+ if (is_windows_7())
+ return;
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
- r = cubeb_init(&ctx[i], "test_sanity");
- assert(r == 0 && ctx[i]);
+ r = common_init(&ctx[i], "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx[i], nullptr);
for (j = 0; j < streams_per_ctx; ++j) {
r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0);
- assert(stream[i * streams_per_ctx + j]);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i * streams_per_ctx + j], nullptr);
}
}
@@ -350,61 +391,65 @@ test_init_destroy_multiple_contexts_and_streams(void)
}
cubeb_destroy(ctx[i]);
}
-
- END_TEST;
}
-static void
-test_basic_stream_operations(void)
+TEST(cubeb, basic_stream_operations)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
uint64_t position;
+ uint32_t latency;
- BEGIN_TEST;
-
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0 && stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
- /* position and volume before stream has started */
+ /* position and latency before stream has started */
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0 && position == 0);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_start(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
- /* position and volume after while stream running */
+ /* position and latency after while stream running */
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_stop(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
- /* position and volume after stream has stopped */
+ /* position and latency after stream has stopped */
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
-
- END_TEST;
}
-static void
-test_stream_position(void)
+TEST(cubeb, stream_position)
{
size_t i;
int r;
@@ -413,60 +458,61 @@ test_stream_position(void)
cubeb_stream_params params;
uint64_t position, last_position;
- BEGIN_TEST;
-
total_frames_written = 0;
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
- assert(r == 0 && stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
/* stream position should not advance before starting playback */
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0 && position == 0);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
delay(500);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0 && position == 0);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
/* stream position should advance during playback */
r = cubeb_stream_start(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
/* XXX let start happen */
delay(500);
/* stream should have prefilled */
- assert(total_frames_written > 0);
+ ASSERT_TRUE(total_frames_written.load() > 0);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
last_position = position;
delay(500);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
- assert(position >= last_position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_GE(position, last_position);
last_position = position;
/* stream position should not exceed total frames written */
for (i = 0; i < 5; ++i) {
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
- assert(position >= last_position);
- assert(position <= total_frames_written);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_GE(position, last_position);
+ ASSERT_LE(position, total_frames_written.load());
last_position = position;
delay(500);
}
@@ -475,60 +521,55 @@ test_stream_position(void)
* stopping the stream. */
for (i = 0; i < 5; ++i) {
r = cubeb_stream_stop(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
- assert(last_position < position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_TRUE(last_position < position);
last_position = position;
delay(500);
r = cubeb_stream_start(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
delay(500);
}
- assert(last_position != 0);
+ ASSERT_NE(last_position, 0u);
/* stream position should not advance after stopping playback */
r = cubeb_stream_stop(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
/* XXX allow stream to settle */
delay(500);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
last_position = position;
delay(500);
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
- assert(position == last_position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, last_position);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
-
- END_TEST;
}
-static int do_drain;
-static int got_drain;
+static std::atomic<int> do_drain;
+static std::atomic<int> got_drain;
static long
test_drain_data_callback(cubeb_stream * stm, void * user_ptr, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
{
- assert(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
+ EXPECT_TRUE(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
+ assert(outputbuffer);
if (do_drain == 1) {
do_drain = 2;
return 0;
}
/* once drain has started, callback must never be called again */
- assert(do_drain != 2);
-#if (defined(_WIN32) || defined(__WIN32__))
- memset(outputbuffer, 0, nframes * sizeof(float));
-#else
+ EXPECT_TRUE(do_drain != 2);
memset(outputbuffer, 0, nframes * sizeof(short));
-#endif
total_frames_written += nframes;
return nframes;
}
@@ -537,13 +578,12 @@ void
test_drain_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state state)
{
if (state == CUBEB_STATE_DRAINED) {
- assert(!got_drain);
+ ASSERT_TRUE(!got_drain);
got_drain = 1;
}
}
-static void
-test_drain(void)
+TEST(cubeb, drain)
{
int r;
cubeb * ctx;
@@ -551,126 +591,102 @@ test_drain(void)
cubeb_stream_params params;
uint64_t position;
- BEGIN_TEST;
-
+ delay_callback = 0;
total_frames_written = 0;
- r = cubeb_init(&ctx, "test_sanity");
- assert(r == 0 && ctx);
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
-#if defined(__ANDROID__)
- params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
-#endif
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_drain_data_callback, test_drain_state_callback, &dummy);
- assert(r == 0 && stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
r = cubeb_stream_start(stream);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
- delay(500);
+ delay(5000);
do_drain = 1;
for (;;) {
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
+ ASSERT_EQ(r, CUBEB_OK);
if (got_drain) {
break;
} else {
- assert(position <= total_frames_written);
+ ASSERT_LE(position, total_frames_written.load());
}
delay(500);
}
r = cubeb_stream_get_position(stream, &position);
- assert(r == 0);
- assert(got_drain);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_TRUE(got_drain);
// Really, we should be able to rely on position reaching our final written frame, but
// for now let's make sure it doesn't continue beyond that point.
- //assert(position <= total_frames_written);
+ //ASSERT_LE(position, total_frames_written.load());
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
- END_TEST;
+ got_drain = 0;
+ do_drain = 0;
}
-int is_windows_7()
+TEST(cubeb, DISABLED_eos_during_prefill)
{
-#ifdef __MINGW32__
- printf("Warning: this test was built with MinGW.\n"
- "MinGW does not contain necessary version checking infrastructure. Claiming to be Windows 7, even if we're not.\n");
- return 1;
-#endif
-#if (defined(_WIN32) || defined(__WIN32__)) && ( !defined(__MINGW32__))
- OSVERSIONINFOEX osvi;
- DWORDLONG condition_mask = 0;
+ // This test needs to be implemented.
+}
- ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+TEST(cubeb, DISABLED_stream_destroy_pending_drain)
+{
+ // This test needs to be implemented.
+}
- // NT 6.1 is Windows 7
- osvi.dwMajorVersion = 6;
- osvi.dwMinorVersion = 1;
+TEST(cubeb, stable_devid)
+{
+ /* Test that the devid field of cubeb_device_info is stable
+ * (ie. compares equal) over two invocations of
+ * cubeb_enumerate_devices(). */
- VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_EQUAL);
- VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
+ int r;
+ cubeb * ctx;
+ cubeb_device_collection first;
+ cubeb_device_collection second;
+ cubeb_device_type all_devices =
+ (cubeb_device_type) (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT);
+ size_t n;
- return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, condition_mask);
-#else
- return 0;
-#endif
-}
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
-int
-main(int /*argc*/, char * /*argv*/[])
-{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_sanity");
-#endif
+ r = cubeb_enumerate_devices(ctx, all_devices, &first);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED)
+ return;
- test_init_destroy_context();
- test_init_destroy_multiple_contexts();
- test_context_variables();
- test_init_destroy_stream();
- test_init_destroy_multiple_streams();
- test_configure_stream();
- test_basic_stream_operations();
- test_stream_position();
+ ASSERT_EQ(r, CUBEB_OK);
- /* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and
- * calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is
- * the HRESULT value for "Cannot create a file when that file already exists",
- * and is not documented as a possible return value for this call. Hence, we
- * try to limit the number of streams we create in this test. */
- if (!is_windows_7()) {
- test_init_destroy_multiple_contexts_and_streams();
+ r = cubeb_enumerate_devices(ctx, all_devices, &second);
+ ASSERT_EQ(r, CUBEB_OK);
- delay_callback = 0;
- test_init_start_stop_destroy_multiple_streams(0, 0);
- test_init_start_stop_destroy_multiple_streams(1, 0);
- test_init_start_stop_destroy_multiple_streams(0, 150);
- test_init_start_stop_destroy_multiple_streams(1, 150);
- delay_callback = 1;
- test_init_start_stop_destroy_multiple_streams(0, 0);
- test_init_start_stop_destroy_multiple_streams(1, 0);
- test_init_start_stop_destroy_multiple_streams(0, 150);
- test_init_start_stop_destroy_multiple_streams(1, 150);
+ ASSERT_EQ(first.count, second.count);
+ for (n = 0; n < first.count; n++) {
+ ASSERT_EQ(first.device[n].devid, second.device[n].devid);
}
- delay_callback = 0;
- test_drain();
-/*
- to implement:
- test_eos_during_prefill();
- test_stream_destroy_pending_drain();
-*/
- printf("\n");
- return 0;
+ r = cubeb_device_collection_destroy(ctx, &first);
+ ASSERT_EQ(r, CUBEB_OK);
+ r = cubeb_device_collection_destroy(ctx, &second);
+ ASSERT_EQ(r, CUBEB_OK);
+ cubeb_destroy(ctx);
}
diff --git a/media/libcubeb/tests/test_tone.cpp b/media/libcubeb/gtest/test_tone.cpp
index 3c6e0ec548..70a71885e6 100644
--- a/media/libcubeb/tests/test_tone.cpp
+++ b/media/libcubeb/gtest/test_tone.cpp
@@ -6,42 +6,35 @@
*/
/* libcubeb api/function test. Plays a simple tone. */
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
+#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
-#include <assert.h>
+#include <memory>
#include <limits.h>
-
#include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
#include "common.h"
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
+
#define SAMPLE_FREQUENCY 48000
-#if (defined(_WIN32) || defined(__WIN32__))
-#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
-#else
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
-#endif
/* store the phase of the generated waveform */
struct cb_user_data {
- long position;
+ std::atomic<long> position;
};
-long data_cb(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void *outputbuffer, long nframes)
+long data_cb_tone(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void *outputbuffer, long nframes)
{
struct cb_user_data *u = (struct cb_user_data *)user;
-#if (defined(_WIN32) || defined(__WIN32__))
- float *b = (float *)outputbuffer;
-#else
short *b = (short *)outputbuffer;
-#endif
float t1, t2;
int i;
@@ -53,21 +46,12 @@ long data_cb(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void
/* North American dial tone */
t1 = sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY);
t2 = sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY);
-#if (defined(_WIN32) || defined(__WIN32__))
- b[i] = 0.5 * t1;
- b[i] += 0.5 * t2;
-#else
b[i] = (SHRT_MAX / 2) * t1;
b[i] += (SHRT_MAX / 2) * t2;
-#endif
/* European dial tone */
/*
t1 = sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY);
-#if (defined(_WIN32) || defined(__WIN32__))
- b[i] = t1;
-#else
b[i] = SHRT_MAX * t1;
-#endif
*/
}
/* remember our phase to avoid clicking on buffer transitions */
@@ -77,7 +61,7 @@ long data_cb(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void
return nframes;
}
-void state_cb(cubeb_stream *stream, void *user, cubeb_state state)
+void state_cb_tone(cubeb_stream *stream, void *user, cubeb_state state)
{
struct cb_user_data *u = (struct cb_user_data *)user;
@@ -86,64 +70,52 @@ void state_cb(cubeb_stream *stream, void *user, cubeb_state state)
switch (state) {
case CUBEB_STATE_STARTED:
- printf("stream started\n"); break;
+ fprintf(stderr, "stream started\n"); break;
case CUBEB_STATE_STOPPED:
- printf("stream stopped\n"); break;
+ fprintf(stderr, "stream stopped\n"); break;
case CUBEB_STATE_DRAINED:
- printf("stream drained\n"); break;
+ fprintf(stderr, "stream drained\n"); break;
default:
- printf("unknown stream state %d\n", state);
+ fprintf(stderr, "unknown stream state %d\n", state);
}
return;
}
-int main(int /*argc*/, char * /*argv*/[])
+TEST(cubeb, tone)
{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_tone");
-#endif
-
cubeb *ctx;
cubeb_stream *stream;
cubeb_stream_params params;
- struct cb_user_data *user_data;
int r;
- r = cubeb_init(&ctx, "Cubeb tone example");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- return r;
- }
+ r = common_init(&ctx, "Cubeb tone example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
params.format = STREAM_FORMAT;
params.rate = SAMPLE_FREQUENCY;
params.channels = 1;
+ params.layout = CUBEB_LAYOUT_MONO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<cb_user_data> user_data(new cb_user_data());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
- user_data = (struct cb_user_data *) malloc(sizeof(*user_data));
- if (user_data == NULL) {
- fprintf(stderr, "Error allocating user data\n");
- return CUBEB_ERROR;
- }
user_data->position = 0;
r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL, &params,
- 4096, data_cb, state_cb, user_data);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb stream\n");
- return r;
- }
+ 4096, data_cb_tone, state_cb_tone, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
cubeb_stream_start(stream);
- delay(500);
+ delay(5000);
cubeb_stream_stop(stream);
- cubeb_stream_destroy(stream);
- cubeb_destroy(ctx);
-
- assert(user_data->position);
-
- free(user_data);
-
- return CUBEB_OK;
+ ASSERT_TRUE(user_data->position.load());
}
diff --git a/media/libcubeb/gtest/test_utils.cpp b/media/libcubeb/gtest/test_utils.cpp
new file mode 100644
index 0000000000..cbdb960984
--- /dev/null
+++ b/media/libcubeb/gtest/test_utils.cpp
@@ -0,0 +1,72 @@
+#include "gtest/gtest.h"
+#include "cubeb_utils.h"
+
+TEST(cubeb, auto_array)
+{
+ auto_array<uint32_t> array;
+ auto_array<uint32_t> array2(10);
+ uint32_t a[10];
+
+ ASSERT_EQ(array2.length(), 0u);
+ ASSERT_EQ(array2.capacity(), 10u);
+
+
+ for (uint32_t i = 0; i < 10; i++) {
+ a[i] = i;
+ }
+
+ ASSERT_EQ(array.capacity(), 0u);
+ ASSERT_EQ(array.length(), 0u);
+
+ array.push(a, 10);
+
+ ASSERT_TRUE(!array.reserve(9));
+
+ for (uint32_t i = 0; i < 10; i++) {
+ ASSERT_EQ(array.data()[i], i);
+ }
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 10u);
+
+ uint32_t b[10];
+
+ array.pop(b, 5);
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 5u);
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(b[i], i);
+ ASSERT_EQ(array.data()[i], 5 + i);
+ }
+ uint32_t* bb = b + 5;
+ array.pop(bb, 5);
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 0u);
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(bb[i], 5 + i);
+ }
+
+ ASSERT_TRUE(!array.pop(nullptr, 1));
+
+ array.push(a, 10);
+ array.push(a, 10);
+
+ for (uint32_t j = 0; j < 2; j++) {
+ for (uint32_t i = 0; i < 10; i++) {
+ ASSERT_EQ(array.data()[10 * j + i], i);
+ }
+ }
+ ASSERT_EQ(array.length(), 20u);
+ ASSERT_EQ(array.capacity(), 20u);
+ array.pop(nullptr, 5);
+
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(array.data()[i], 5 + i);
+ }
+
+ ASSERT_EQ(array.length(), 15u);
+ ASSERT_EQ(array.capacity(), 20u);
+}
+
diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h
index 449b39c55e..f653f5b7d1 100644
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -7,8 +7,9 @@
#if !defined(CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382)
#define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382
-#include <stdint.h>
#include "cubeb_export.h"
+#include <stdint.h>
+#include <stdlib.h>
#if defined(__cplusplus)
extern "C" {
@@ -30,19 +31,13 @@ extern "C" {
@code
cubeb * app_ctx;
- cubeb_init(&app_ctx, "Example Application");
+ cubeb_init(&app_ctx, "Example Application", NULL);
int rv;
- int rate;
- int latency_frames;
+ uint32_t rate;
+ uint32_t latency_frames;
uint64_t ts;
- rv = cubeb_get_min_latency(app_ctx, output_params, &latency_frames);
- if (rv != CUBEB_OK) {
- fprintf(stderr, "Could not get minimum latency");
- return rv;
- }
-
- rv = cubeb_get_preferred_sample_rate(app_ctx, output_params, &rate);
+ rv = cubeb_get_preferred_sample_rate(app_ctx, &rate);
if (rv != CUBEB_OK) {
fprintf(stderr, "Could not get preferred sample-rate");
return rv;
@@ -52,16 +47,26 @@ extern "C" {
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
output_params.rate = rate;
output_params.channels = 2;
+ output_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get minimum latency");
+ return rv;
+ }
cubeb_stream_params input_params;
- output_params.format = CUBEB_SAMPLE_FLOAT32NE;
- output_params.rate = rate;
- output_params.channels = 1;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = rate;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
cubeb_stream * stm;
rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1",
- NULL, input_params,
- NULL, output_params,
+ NULL, &input_params,
+ NULL, &output_params,
latency_frames,
data_cb, state_cb,
NULL);
@@ -92,14 +97,14 @@ extern "C" {
@code
long data_cb(cubeb_stream * stm, void * user,
- void * input_buffer, void * output_buffer, long nframes)
+ const void * input_buffer, void * output_buffer, long nframes)
{
- float * in = input_buffer;
+ const float * in = input_buffer;
float * out = output_buffer;
- for (i = 0; i < nframes; ++i) {
- for (c = 0; c < 2; ++c) {
- buf[i][c] = in[i];
+ for (int i = 0; i < nframes; ++i) {
+ for (int c = 0; c < 2; ++c) {
+ out[2 * i + c] = in[i];
}
}
return nframes;
@@ -117,8 +122,10 @@ extern "C" {
/** @file
The <tt>libcubeb</tt> C API. */
-typedef struct cubeb cubeb; /**< Opaque handle referencing the application state. */
-typedef struct cubeb_stream cubeb_stream; /**< Opaque handle referencing the stream state. */
+typedef struct cubeb
+ cubeb; /**< Opaque handle referencing the application state. */
+typedef struct cubeb_stream
+ cubeb_stream; /**< Opaque handle referencing the stream state. */
/** Sample format enumeration. */
typedef enum {
@@ -143,54 +150,126 @@ typedef enum {
#endif
} cubeb_sample_format;
-#if defined(__ANDROID__)
-/**
- * This maps to the underlying stream types on supported platforms, e.g.
- * Android.
- */
-typedef enum {
- CUBEB_STREAM_TYPE_VOICE_CALL = 0,
- CUBEB_STREAM_TYPE_SYSTEM = 1,
- CUBEB_STREAM_TYPE_RING = 2,
- CUBEB_STREAM_TYPE_MUSIC = 3,
- CUBEB_STREAM_TYPE_ALARM = 4,
- CUBEB_STREAM_TYPE_NOTIFICATION = 5,
- CUBEB_STREAM_TYPE_BLUETOOTH_SCO = 6,
- CUBEB_STREAM_TYPE_SYSTEM_ENFORCED = 7,
- CUBEB_STREAM_TYPE_DTMF = 8,
- CUBEB_STREAM_TYPE_TTS = 9,
- CUBEB_STREAM_TYPE_FM = 10,
-
- CUBEB_STREAM_TYPE_MAX
-} cubeb_stream_type;
-#endif
-
/** An opaque handle used to refer a particular input or output device
* across calls. */
-typedef void * cubeb_devid;
+typedef void const * cubeb_devid;
/** Level (verbosity) of logging for a particular cubeb context. */
typedef enum {
CUBEB_LOG_DISABLED = 0, /** < Logging disabled */
- CUBEB_LOG_NORMAL = 1, /**< Logging lifetime operation (creation/destruction). */
- CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */
+ CUBEB_LOG_NORMAL =
+ 1, /**< Logging lifetime operation (creation/destruction). */
+ CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance
+ implications. */
} cubeb_log_level;
+typedef enum {
+ CHANNEL_UNKNOWN = 0,
+ CHANNEL_FRONT_LEFT = 1 << 0,
+ CHANNEL_FRONT_RIGHT = 1 << 1,
+ CHANNEL_FRONT_CENTER = 1 << 2,
+ CHANNEL_LOW_FREQUENCY = 1 << 3,
+ CHANNEL_BACK_LEFT = 1 << 4,
+ CHANNEL_BACK_RIGHT = 1 << 5,
+ CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6,
+ CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7,
+ CHANNEL_BACK_CENTER = 1 << 8,
+ CHANNEL_SIDE_LEFT = 1 << 9,
+ CHANNEL_SIDE_RIGHT = 1 << 10,
+ CHANNEL_TOP_CENTER = 1 << 11,
+ CHANNEL_TOP_FRONT_LEFT = 1 << 12,
+ CHANNEL_TOP_FRONT_CENTER = 1 << 13,
+ CHANNEL_TOP_FRONT_RIGHT = 1 << 14,
+ CHANNEL_TOP_BACK_LEFT = 1 << 15,
+ CHANNEL_TOP_BACK_CENTER = 1 << 16,
+ CHANNEL_TOP_BACK_RIGHT = 1 << 17
+} cubeb_channel;
+
+typedef uint32_t cubeb_channel_layout;
+// Some common layout definitions.
+enum {
+ CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined.
+ CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT,
+ CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F =
+ CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_2F1 =
+ CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER,
+ CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER,
+ CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT,
+ CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT |
+ CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+ CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT |
+ CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+};
+
+/** Miscellaneous stream preferences. */
+typedef enum {
+ CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */
+ CUBEB_STREAM_PREF_LOOPBACK =
+ 0x01, /**< Request a loopback stream. Should be
+ specified on the input params and an
+ output device to loopback from should
+ be passed in place of an input device. */
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching
+ default device on OS
+ changes. */
+ CUBEB_STREAM_PREF_VOICE =
+ 0x04, /**< This stream is going to transport voice data.
+ Depending on the backend and platform, this can
+ change the audio input or output devices
+ selected, as well as the quality of the stream,
+ for example to accomodate bluetooth SCO modes on
+ bluetooth devices. */
+ CUBEB_STREAM_PREF_RAW =
+ 0x08, /**< Windows only. Bypass all signal processing
+ except for always on APO, driver and hardware. */
+ CUBEB_STREAM_PREF_PERSIST = 0x10, /**< Request that the volume and mute
+ settings should persist across restarts
+ of the stream and/or application. This is
+ obsolete and ignored by all backends. */
+ CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT = 0x20 /**< Don't automatically try to
+ connect ports. Only affects
+ the jack backend. */
+} cubeb_stream_prefs;
+
/** Stream format initialization parameters. */
typedef struct {
cubeb_sample_format format; /**< Requested sample format. One of
#cubeb_sample_format. */
- unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
- unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */
-#if defined(__ANDROID__)
- cubeb_stream_type stream_type; /**< Used to map Android audio stream types */
-#endif
+ uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
+ uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */
+ cubeb_channel_layout
+ layout; /**< Requested channel layout. This must be consistent with the
+ provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */
+ cubeb_stream_prefs prefs; /**< Requested preferences. */
} cubeb_stream_params;
/** Audio device description */
typedef struct {
char * output_name; /**< The name of the output device */
- char * input_name; /**< The name of the input device */
+ char * input_name; /**< The name of the input device */
} cubeb_device;
/** Stream states signaled via state_callback. */
@@ -203,12 +282,15 @@ typedef enum {
/** Result code enumeration. */
enum {
- CUBEB_OK = 0, /**< Success. */
- CUBEB_ERROR = -1, /**< Unclassified error. */
- CUBEB_ERROR_INVALID_FORMAT = -2, /**< Unsupported #cubeb_stream_params requested. */
+ CUBEB_OK = 0, /**< Success. */
+ CUBEB_ERROR = -1, /**< Unclassified error. */
+ CUBEB_ERROR_INVALID_FORMAT =
+ -2, /**< Unsupported #cubeb_stream_params requested. */
CUBEB_ERROR_INVALID_PARAMETER = -3, /**< Invalid parameter specified. */
- CUBEB_ERROR_NOT_SUPPORTED = -4, /**< Optional function not implemented in current backend. */
- CUBEB_ERROR_DEVICE_UNAVAILABLE = -5 /**< Device specified by #cubeb_devid not available. */
+ CUBEB_ERROR_NOT_SUPPORTED =
+ -4, /**< Optional function not implemented in current backend. */
+ CUBEB_ERROR_DEVICE_UNAVAILABLE =
+ -5 /**< Device specified by #cubeb_devid not available. */
};
/**
@@ -224,82 +306,96 @@ typedef enum {
* The state of a device.
*/
typedef enum {
- CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system level. */
- CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is plugged into it. */
- CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */
+ CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system
+ level. */
+ CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is
+ plugged into it. */
+ CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */
} cubeb_device_state;
/**
* Architecture specific sample type.
*/
typedef enum {
- CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */
- CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */
- CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */
- CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */
+ CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */
+ CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */
+ CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */
+ CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */
} cubeb_device_fmt;
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
/** 16-bit integers, native endianess, when on a Big Endian environment. */
-#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE
-/** 32-bit floating points, native endianess, when on a Big Endian environment. */
-#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE
+#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE
+/** 32-bit floating points, native endianess, when on a Big Endian environment.
+ */
+#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE
#else
/** 16-bit integers, native endianess, when on a Little Endian environment. */
-#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE
+#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE
/** 32-bit floating points, native endianess, when on a Little Endian
* environment. */
-#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE
+#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE
#endif
/** All the 16-bit integers types. */
-#define CUBEB_DEVICE_FMT_S16_MASK (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
+#define CUBEB_DEVICE_FMT_S16_MASK \
+ (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
/** All the 32-bit floating points types. */
-#define CUBEB_DEVICE_FMT_F32_MASK (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
+#define CUBEB_DEVICE_FMT_F32_MASK \
+ (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
/** All the device formats types. */
-#define CUBEB_DEVICE_FMT_ALL (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK)
+#define CUBEB_DEVICE_FMT_ALL \
+ (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK)
/** Channel type for a `cubeb_stream`. Depending on the backend and platform
* used, this can control inter-stream interruption, ducking, and volume
* control.
*/
typedef enum {
- CUBEB_DEVICE_PREF_NONE = 0x00,
- CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
- CUBEB_DEVICE_PREF_VOICE = 0x02,
- CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
- CUBEB_DEVICE_PREF_ALL = 0x0F
+ CUBEB_DEVICE_PREF_NONE = 0x00,
+ CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
+ CUBEB_DEVICE_PREF_VOICE = 0x02,
+ CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
+ CUBEB_DEVICE_PREF_ALL = 0x0F
} cubeb_device_pref;
/** This structure holds the characteristics
- * of an input or output audio device. It can be obtained using
- * `cubeb_enumerate_devices`, and must be destroyed using
- * `cubeb_device_info_destroy`. */
+ * of an input or output audio device. It is obtained using
+ * `cubeb_enumerate_devices`, which returns these structures via
+ * `cubeb_device_collection` and must be destroyed via
+ * `cubeb_device_collection_destroy`. */
typedef struct {
- cubeb_devid devid; /**< Device identifier handle. */
- char * device_id; /**< Device identifier which might be presented in a UI. */
- char * friendly_name; /**< Friendly device name which might be presented in a UI. */
- char * group_id; /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */
- char * vendor_name; /**< Optional vendor name, may be NULL. */
-
- cubeb_device_type type; /**< Type of device (Input/Output). */
- cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */
- cubeb_device_pref preferred;/**< Preferred device. */
-
- cubeb_device_fmt format; /**< Sample format supported. */
- cubeb_device_fmt default_format; /**< The default sample format for this device. */
- unsigned int max_channels; /**< Channels. */
- unsigned int default_rate; /**< Default/Preferred sample rate. */
- unsigned int max_rate; /**< Maximum sample rate supported. */
- unsigned int min_rate; /**< Minimum sample rate supported. */
-
- unsigned int latency_lo; /**< Lowest possible latency in frames. */
- unsigned int latency_hi; /**< Higest possible latency in frames. */
+ cubeb_devid devid; /**< Device identifier handle. */
+ char const *
+ device_id; /**< Device identifier which might be presented in a UI. */
+ char const * friendly_name; /**< Friendly device name which might be presented
+ in a UI. */
+ char const * group_id; /**< Two devices have the same group identifier if they
+ belong to the same physical device; for example a
+ headset and microphone. */
+ char const * vendor_name; /**< Optional vendor name, may be NULL. */
+
+ cubeb_device_type type; /**< Type of device (Input/Output). */
+ cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */
+ cubeb_device_pref preferred; /**< Preferred device. */
+
+ cubeb_device_fmt format; /**< Sample format supported. */
+ cubeb_device_fmt
+ default_format; /**< The default sample format for this device. */
+ uint32_t max_channels; /**< Channels. */
+ uint32_t default_rate; /**< Default/Preferred sample rate. */
+ uint32_t max_rate; /**< Maximum sample rate supported. */
+ uint32_t min_rate; /**< Minimum sample rate supported. */
+
+ uint32_t latency_lo; /**< Lowest possible latency in frames. */
+ uint32_t latency_hi; /**< Higest possible latency in frames. */
} cubeb_device_info;
-/** Device collection. */
+/** Device collection.
+ * Returned by `cubeb_enumerate_devices` and destroyed by
+ * `cubeb_device_collection_destroy`. */
typedef struct {
- uint32_t count; /**< Device count in collection. */
- cubeb_device_info * device[1]; /**< Array of pointers to device info. */
+ cubeb_device_info * device; /**< Array of pointers to device info. */
+ size_t count; /**< Device count in collection. */
} cubeb_device_collection;
/** User supplied data callback.
@@ -316,54 +412,68 @@ typedef struct {
@param output_buffer A pointer to a buffer to be filled with audio samples,
or nullptr if this is an input-only stream.
@param nframes The number of frames of the two buffer.
- @retval Number of frames written to the output buffer. If this number is
- less than nframes, then the stream will start to drain.
+ @retval If the stream has output, this is the number of frames written to
+ the output buffer. In this case, if this number is less than
+ nframes then the stream will start to drain. If the stream is
+ input only, then returning nframes indicates data has been read.
+ In this case, a value less than nframes will result in the stream
+ being stopped.
@retval CUBEB_ERROR on error, in which case the data callback will stop
and the stream will enter a shutdown state. */
-typedef long (* cubeb_data_callback)(cubeb_stream * stream,
- void * user_ptr,
- const void * input_buffer,
- void * output_buffer,
- long nframes);
+typedef long (*cubeb_data_callback)(cubeb_stream * stream, void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer, long nframes);
/** User supplied state callback.
@param stream The stream for this this callback fired.
@param user_ptr The pointer passed to cubeb_stream_init.
@param state The new state of the stream. */
-typedef void (* cubeb_state_callback)(cubeb_stream * stream,
- void * user_ptr,
- cubeb_state state);
+typedef void (*cubeb_state_callback)(cubeb_stream * stream, void * user_ptr,
+ cubeb_state state);
/**
* User supplied callback called when the underlying device changed.
* @param user The pointer passed to cubeb_stream_init. */
-typedef void (* cubeb_device_changed_callback)(void * user_ptr);
+typedef void (*cubeb_device_changed_callback)(void * user_ptr);
/**
* User supplied callback called when the underlying device collection changed.
* @param context A pointer to the cubeb context.
- * @param user_ptr The pointer passed to cubeb_stream_init. */
-typedef void (* cubeb_device_collection_changed_callback)(cubeb * context,
- void * user_ptr);
+ * @param user_ptr The pointer passed to
+ * cubeb_register_device_collection_changed. */
+typedef void (*cubeb_device_collection_changed_callback)(cubeb * context,
+ void * user_ptr);
/** User supplied callback called when a message needs logging. */
-typedef void (* cubeb_log_callback)(const char * fmt, ...);
+typedef void (*cubeb_log_callback)(char const * fmt, ...);
/** Initialize an application context. This will perform any library or
application scoped initialization.
+
+ Note: On Windows platforms, COM must be initialized in MTA mode on
+ any thread that will call the cubeb API.
+
@param context A out param where an opaque pointer to the application
context will be returned.
@param context_name A name for the context. Depending on the platform this
can appear in different locations.
+ @param backend_name The name of the cubeb backend user desires to select.
+ Accepted values self-documented in cubeb.c: init_oneshot
+ If NULL, a default ordering is used for backend choice.
+ A valid choice overrides all other possible backends,
+ so long as the backend was included at compile time.
@retval CUBEB_OK in case of success.
@retval CUBEB_ERROR in case of error, for example because the host
has no audio hardware. */
-CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name);
+CUBEB_EXPORT int
+cubeb_init(cubeb ** context, char const * context_name,
+ char const * backend_name);
/** Get a read-only string identifying this context's current backend.
@param context A pointer to the cubeb context.
@retval Read-only string identifying current backend. */
-CUBEB_EXPORT char const * cubeb_get_backend_id(cubeb * context);
+CUBEB_EXPORT char const *
+cubeb_get_backend_id(cubeb * context);
/** Get the maximum possible number of channels.
@param context A pointer to the cubeb context.
@@ -372,11 +482,12 @@ CUBEB_EXPORT char const * cubeb_get_backend_id(cubeb * context);
@retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED
@retval CUBEB_ERROR */
-CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
+CUBEB_EXPORT int
+cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
/** Get the minimal latency value, in frames, that is guaranteed to work
when creating a stream for the specified sample rate. This is platform,
- hardware and backend dependant.
+ hardware and backend dependent.
@param context A pointer to the cubeb context.
@param params On some backends, the minimum achievable latency depends on
the characteristics of the stream.
@@ -385,23 +496,25 @@ CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_cha
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
- cubeb_stream_params params,
- uint32_t * latency_frames);
+CUBEB_EXPORT int
+cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
+ uint32_t * latency_frames);
/** Get the preferred sample rate for this backend: this is hardware and
- platform dependant, and can avoid resampling, and/or trigger fastpaths.
+ platform dependent, and can avoid resampling, and/or trigger fastpaths.
@param context A pointer to the cubeb context.
@param rate The samplerate (in Hz) the current configuration prefers.
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
+CUBEB_EXPORT int
+cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
/** Destroy an application context. This must be called after all stream have
* been destroyed.
@param context A pointer to the cubeb context.*/
-CUBEB_EXPORT void cubeb_destroy(cubeb * context);
+CUBEB_EXPORT void
+cubeb_destroy(cubeb * context);
/** Initialize a stream associated with the supplied application context.
@param context A pointer to the cubeb context.
@@ -409,13 +522,21 @@ CUBEB_EXPORT void cubeb_destroy(cubeb * context);
cubeb stream.
@param stream_name A name for this stream.
@param input_device Device for the input side of the stream. If NULL the
- default input device is used.
+ default input device is used. Passing a valid
+ cubeb_devid means the stream only ever uses that device. Passing a NULL
+ cubeb_devid allows the stream to follow that device
+ type's OS default.
@param input_stream_params Parameters for the input side of the stream, or
NULL if this stream is output only.
@param output_device Device for the output side of the stream. If NULL the
- default output device is used.
+ default output device is used. Passing a valid
+ cubeb_devid means the stream only ever uses that device. Passing a NULL
+ cubeb_devid allows the stream to follow that device
+ type's OS default.
@param output_stream_params Parameters for the output side of the stream, or
- NULL if this stream is input only.
+ NULL if this stream is input only. When input
+ and output stream parameters are supplied, their
+ rate has to be the same.
@param latency_frames Stream latency in frames. Valid range
is [1, 96000].
@param data_callback Will be called to preroll data before playback is
@@ -427,41 +548,42 @@ CUBEB_EXPORT void cubeb_destroy(cubeb * context);
@retval CUBEB_ERROR
@retval CUBEB_ERROR_INVALID_FORMAT
@retval CUBEB_ERROR_DEVICE_UNAVAILABLE */
-CUBEB_EXPORT int cubeb_stream_init(cubeb * context,
- cubeb_stream ** stream,
- char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr);
+CUBEB_EXPORT int
+cubeb_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ uint32_t latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr);
/** Destroy a stream. `cubeb_stream_stop` MUST be called before destroying a
stream.
@param stream The stream to destroy. */
-CUBEB_EXPORT void cubeb_stream_destroy(cubeb_stream * stream);
+CUBEB_EXPORT void
+cubeb_stream_destroy(cubeb_stream * stream);
/** Start playback.
@param stream
@retval CUBEB_OK
@retval CUBEB_ERROR */
-CUBEB_EXPORT int cubeb_stream_start(cubeb_stream * stream);
+CUBEB_EXPORT int
+cubeb_stream_start(cubeb_stream * stream);
/** Stop playback.
@param stream
@retval CUBEB_OK
@retval CUBEB_ERROR */
-CUBEB_EXPORT int cubeb_stream_stop(cubeb_stream * stream);
+CUBEB_EXPORT int
+cubeb_stream_stop(cubeb_stream * stream);
/** Get the current stream playback position.
@param stream
@param position Playback position in frames.
@retval CUBEB_OK
@retval CUBEB_ERROR */
-CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
+CUBEB_EXPORT int
+cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
/** Get the latency for this stream, in frames. This is the number of frames
between the time cubeb acquires the data in the callback and the listener
@@ -471,8 +593,20 @@ CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * pos
@retval CUBEB_OK
@retval CUBEB_ERROR_NOT_SUPPORTED
@retval CUBEB_ERROR */
-CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
+CUBEB_EXPORT int
+cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
+/** Get the input latency for this stream, in frames. This is the number of
+ frames between the time the audio input devices records the data, and they
+ are available in the data callback.
+ This returns CUBEB_ERROR when the stream is output-only.
+ @param stream
+ @param latency Current approximate stream latency in frames.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_NOT_SUPPORTED
+ @retval CUBEB_ERROR */
+CUBEB_EXPORT int
+cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency);
/** Set the volume for a stream.
@param stream the stream for which to adjust the volume.
@param volume a float between 0.0 (muted) and 1.0 (maximum volume)
@@ -480,21 +614,17 @@ CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * late
@retval CUBEB_ERROR_INVALID_PARAMETER volume is outside [0.0, 1.0] or
stream is an invalid pointer
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume);
-
-/** If the stream is stereo, set the left/right panning. If the stream is mono,
- this has no effect.
- @param stream the stream for which to change the panning
- @param panning a number from -1.0 to 1.0. -1.0 means that the stream is
- fully mixed in the left channel, 1.0 means the stream is fully
- mixed in the right channel. 0.0 is equal power in the right and
- left channel (default).
+CUBEB_EXPORT int
+cubeb_stream_set_volume(cubeb_stream * stream, float volume);
+
+/** Change a stream's name.
+ @param stream the stream for which to set the name.
+ @param stream_name the new name for the stream
@retval CUBEB_OK
- @retval CUBEB_ERROR_INVALID_PARAMETER if stream is null or if panning is
- outside the [-1.0, 1.0] range.
- @retval CUBEB_ERROR_NOT_SUPPORTED
- @retval CUBEB_ERROR stream is not mono nor stereo */
-CUBEB_EXPORT int cubeb_stream_set_panning(cubeb_stream * stream, float panning);
+ @retval CUBEB_ERROR_INVALID_PARAMETER if any pointer is invalid
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name);
/** Get the current output device for this stream.
@param stm the stream for which to query the current output device
@@ -503,8 +633,9 @@ CUBEB_EXPORT int cubeb_stream_set_panning(cubeb_stream * stream, float panning);
@retval CUBEB_ERROR_INVALID_PARAMETER if either stm, device or count are
invalid pointers
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_stream_get_current_device(cubeb_stream * stm,
- cubeb_device ** const device);
+CUBEB_EXPORT int
+cubeb_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device);
/** Destroy a cubeb_device structure.
@param stream the stream passed in cubeb_stream_get_current_device
@@ -512,8 +643,8 @@ CUBEB_EXPORT int cubeb_stream_get_current_device(cubeb_stream * stm,
@retval CUBEB_OK in case of success
@retval CUBEB_ERROR_INVALID_PARAMETER if devices is an invalid pointer
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream,
- cubeb_device * devices);
+CUBEB_EXPORT int
+cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * devices);
/** Set a callback to be notified when the output device changes.
@param stream the stream for which to set the callback.
@@ -523,45 +654,57 @@ CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream,
@retval CUBEB_ERROR_INVALID_PARAMETER if either stream or
device_changed_callback are invalid pointers.
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
- cubeb_device_changed_callback device_changed_callback);
+CUBEB_EXPORT int
+cubeb_stream_register_device_changed_callback(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback);
+
+/** Return the user data pointer registered with the stream with
+ cubeb_stream_init.
+ @param stream the stream for which to retrieve user data pointer.
+ @retval user data pointer */
+CUBEB_EXPORT void *
+cubeb_stream_user_ptr(cubeb_stream * stream);
/** Returns enumerated devices.
@param context
@param devtype device type to include
- @param collection output collection. Must be destroyed with cubeb_device_collection_destroy
+ @param collection output collection. Must be destroyed with
+ cubeb_device_collection_destroy
@retval CUBEB_OK in case of success
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_enumerate_devices(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection ** collection);
+CUBEB_EXPORT int
+cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection * collection);
/** Destroy a cubeb_device_collection, and its `cubeb_device_info`.
+ @param context
@param collection collection to destroy
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */
-CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb_device_collection * collection);
-
-/** Destroy a cubeb_device_info structure.
- @param info pointer to device info structure
- @retval CUBEB_OK
- @retval CUBEB_ERROR_INVALID_PARAMETER if info is an invalid pointer */
-CUBEB_EXPORT int cubeb_device_info_destroy(cubeb_device_info * info);
+CUBEB_EXPORT int
+cubeb_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection);
/** Registers a callback which is called when the system detects
a new device or a device is removed.
@param context
- @param devtype device type to include
+ @param devtype device type to include. Different callbacks and user pointers
+ can be registered for each devtype. The hybrid devtype
+ `CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid
+ and will register the provided callback and user pointer in both
+ sides.
@param callback a function called whenever the system device list changes.
- Passing NULL allow to unregister a function
+ Passing NULL allow to unregister a function. You have to unregister
+ first before you register a new callback.
@param user_ptr pointer to user specified data which will be present in
subsequent callbacks.
@retval CUBEB_ERROR_NOT_SUPPORTED */
-CUBEB_EXPORT int cubeb_register_device_collection_changed(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback callback,
- void * user_ptr);
+CUBEB_EXPORT int
+cubeb_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback callback, void * user_ptr);
/** Set a callback to be called with a message.
@param log_level CUBEB_LOG_VERBOSE, CUBEB_LOG_NORMAL.
@@ -571,8 +714,9 @@ CUBEB_EXPORT int cubeb_register_device_collection_changed(cubeb * context,
@retval CUBEB_ERROR_INVALID_PARAMETER if either context or log_callback are
invalid pointers, or if level is not
in cubeb_log_level. */
-CUBEB_EXPORT int cubeb_set_log_callback(cubeb_log_level log_level,
- cubeb_log_callback log_callback);
+CUBEB_EXPORT int
+cubeb_set_log_callback(cubeb_log_level log_level,
+ cubeb_log_callback log_callback);
#if defined(__cplusplus)
}
diff --git a/media/libcubeb/osx-linearize-operations.patch b/media/libcubeb/osx-linearize-operations.patch
deleted file mode 100644
index 9f4f31bcaf..0000000000
--- a/media/libcubeb/osx-linearize-operations.patch
+++ /dev/null
@@ -1,968 +0,0 @@
-From: Paul Adenot <paul@paul.cx>
-Subject: Linearize operations on AudioUnits to sidestep a deadlock.
-
----
-
-diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp
---- a/src/cubeb_audiounit.cpp
-+++ b/src/cubeb_audiounit.cpp
-@@ -53,40 +53,45 @@ typedef UInt32 AudioFormatFlags;
-
- #define AU_OUT_BUS 0
- #define AU_IN_BUS 1
-
- #define PRINT_ERROR_CODE(str, r) do { \
- LOG("System call failed: %s (rv: %d)", str, r); \
- } while(0)
-
-+const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";
-+
- /* Testing empirically, some headsets report a minimal latency that is very
- * low, but this does not work in practice. Lie and say the minimum is 256
- * frames. */
- const uint32_t SAFE_MIN_LATENCY_FRAMES = 256;
- const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
-
- void audiounit_stream_stop_internal(cubeb_stream * stm);
- void audiounit_stream_start_internal(cubeb_stream * stm);
--static void close_audiounit_stream(cubeb_stream * stm);
--static int setup_audiounit_stream(cubeb_stream * stm);
-+static void audiounit_close_stream(cubeb_stream *stm);
-+static int audiounit_setup_stream(cubeb_stream *stm);
-
- extern cubeb_ops const audiounit_ops;
-
- struct cubeb {
- cubeb_ops const * ops;
- owned_critical_section mutex;
- std::atomic<int> active_streams;
-+ uint32_t global_latency_frames = 0;
- int limit_streams;
- cubeb_device_collection_changed_callback collection_changed_callback;
- void * collection_changed_user_ptr;
- /* Differentiate input from output devices. */
- cubeb_device_type collection_changed_devtype;
- uint32_t devtype_device_count;
- AudioObjectID * devtype_device_array;
-+ // The queue is asynchronously deallocated once all references to it are released
-+ dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
- };
-
- class auto_array_wrapper
- {
- public:
- explicit auto_array_wrapper(auto_array<float> * ar)
- : float_ar(ar)
- , short_ar(nullptr)
-@@ -205,16 +210,17 @@ struct cubeb_stream {
- cubeb_resampler * resampler;
- /* This is the number of output callback we got in a row. This is usually one,
- * but can be two when the input and output rate are different, and more when
- * a device has been plugged or unplugged, as there can be some time before
- * the device is ready. */
- std::atomic<int> output_callback_in_a_row;
- /* This is true if a device change callback is currently running. */
- std::atomic<bool> switching_device;
-+ std::atomic<bool> buffer_size_change_state{ false };
- };
-
- bool has_input(cubeb_stream * stm)
- {
- return stm->input_stream_params.rate != 0;
- }
-
- bool has_output(cubeb_stream * stm)
-@@ -256,16 +262,24 @@ audiotimestamp_to_latency(AudioTimeStamp
-
- uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime);
- uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
-
- return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL;
- }
-
- static void
-+audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames)
-+{
-+ stm->mutex.assert_current_thread_owns();
-+ assert(stm->context->active_streams == 1);
-+ stm->context->global_latency_frames = latency_frames;
-+}
-+
-+static void
- audiounit_make_silent(AudioBuffer * ioData)
- {
- assert(ioData);
- assert(ioData->mData);
- memset(ioData->mData, 0, ioData->mDataByteSize);
- }
-
- static OSStatus
-@@ -576,29 +590,54 @@ audiounit_get_input_device_id(AudioDevic
- device_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- return CUBEB_OK;
- }
-
-+static int
-+audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
-+{
-+ if (is_started) {
-+ audiounit_stream_stop_internal(stm);
-+ }
-+
-+ {
-+ auto_lock lock(stm->mutex);
-+
-+ audiounit_close_stream(stm);
-+
-+ if (audiounit_setup_stream(stm) != CUBEB_OK) {
-+ LOG("(%p) Stream reinit failed.", stm);
-+ return CUBEB_ERROR;
-+ }
-+
-+ // Reset input frames to force new stream pre-buffer
-+ // silence if needed, check `is_extra_input_needed()`
-+ stm->frames_read = 0;
-+
-+ // If the stream was running, start it again.
-+ if (is_started) {
-+ audiounit_stream_start_internal(stm);
-+ }
-+ }
-+ return CUBEB_OK;
-+}
-+
- static OSStatus
- audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
- const AudioObjectPropertyAddress * addresses,
- void * user)
- {
- cubeb_stream * stm = (cubeb_stream*) user;
-- int rv;
-- bool was_running = false;
--
- stm->switching_device = true;
--
- // Note if the stream was running or not
-- was_running = !stm->shutdown;
-+ bool was_running = !stm->shutdown;
-
- LOG("(%p) Audio device changed, %d events.", stm, address_count);
- for (UInt32 i = 0; i < address_count; i++) {
- switch(addresses[i].mSelector) {
- case kAudioHardwarePropertyDefaultOutputDevice: {
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i);
- // Allow restart to choose the new default
- stm->output_device = nullptr;
-@@ -639,38 +678,25 @@ audiounit_property_listener_callback(Aud
- if (stm->device_changed_callback) {
- stm->device_changed_callback(stm->user_ptr);
- }
- break;
- }
- }
- }
-
-- // This means the callback won't be called again.
-- audiounit_stream_stop_internal(stm);
--
-- {
-- auto_lock lock(stm->mutex);
-- close_audiounit_stream(stm);
-- rv = setup_audiounit_stream(stm);
-- if (rv != CUBEB_OK) {
-- LOG("(%p) Could not reopen a stream after switching.", stm);
-+ // Use a new thread, through the queue, to avoid deadlock when calling
-+ // Get/SetProperties method from inside notify callback
-+ dispatch_async(stm->context->serial_queue, ^() {
-+ if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-- return noErr;
-+ LOG("(%p) Could not reopen the stream after switching.", stm);
- }
--
-- stm->frames_read = 0;
--
-- // If the stream was running, start it again.
-- if (was_running) {
-- audiounit_stream_start_internal(stm);
-- }
-- }
--
-- stm->switching_device = false;
-+ stm->switching_device = false;
-+ });
-
- return noErr;
- }
-
- OSStatus
- audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector,
- AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener)
- {
-@@ -1155,18 +1181,17 @@ audiounit_init_input_linear_buffer(cubeb
-
- static void
- audiounit_destroy_input_linear_buffer(cubeb_stream * stream)
- {
- delete stream->input_linear_buffer;
- }
-
- static uint32_t
--audiounit_clamp_latency(cubeb_stream * stm,
-- uint32_t latency_frames)
-+audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
- {
- // For the 1st stream set anything within safe min-max
- assert(stm->context->active_streams > 0);
- if (stm->context->active_streams == 1) {
- return std::max(std::min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
- SAFE_MIN_LATENCY_FRAMES);
- }
-
-@@ -1219,26 +1244,374 @@ audiounit_clamp_latency(cubeb_stream * s
- } else {
- upper_latency_limit = SAFE_MAX_LATENCY_FRAMES;
- }
-
- return std::max(std::min<uint32_t>(latency_frames, upper_latency_limit),
- SAFE_MIN_LATENCY_FRAMES);
- }
-
-+/*
-+ * Change buffer size is prone to deadlock thus we change it
-+ * following the steps:
-+ * - register a listener for the buffer size property
-+ * - change the property
-+ * - wait until the listener is executed
-+ * - property has changed, remove the listener
-+ * */
-+static void
-+buffer_size_changed_callback(void * inClientData,
-+ AudioUnit inUnit,
-+ AudioUnitPropertyID inPropertyID,
-+ AudioUnitScope inScope,
-+ AudioUnitElement inElement)
-+{
-+ cubeb_stream * stm = (cubeb_stream *)inClientData;
-+
-+ AudioUnit au = inUnit;
-+ AudioUnitScope au_scope = kAudioUnitScope_Input;
-+ AudioUnitElement au_element = inElement;
-+ const char * au_type = "output";
-+
-+ if (au == stm->input_unit) {
-+ au_scope = kAudioUnitScope_Output;
-+ au_type = "input";
-+ }
-+
-+ switch (inPropertyID) {
-+
-+ case kAudioDevicePropertyBufferFrameSize: {
-+ if (inScope != au_scope) {
-+ break;
-+ }
-+ UInt32 new_buffer_size;
-+ UInt32 outSize = sizeof(UInt32);
-+ OSStatus r = AudioUnitGetProperty(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ au_scope,
-+ au_element,
-+ &new_buffer_size,
-+ &outSize);
-+ if (r != noErr) {
-+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm);
-+ } else {
-+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm,
-+ au_type, new_buffer_size, inScope);
-+ }
-+ stm->buffer_size_change_state = true;
-+ break;
-+ }
-+ }
-+}
-+
-+enum set_buffer_size_side {
-+ INPUT,
-+ OUTPUT,
-+};
-+
- static int
--setup_audiounit_stream(cubeb_stream * stm)
-+audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side)
-+{
-+ AudioUnit au = stm->output_unit;
-+ AudioUnitScope au_scope = kAudioUnitScope_Input;
-+ AudioUnitElement au_element = AU_OUT_BUS;
-+ const char * au_type = "output";
-+
-+ if (set_side == INPUT) {
-+ au = stm->input_unit;
-+ au_scope = kAudioUnitScope_Output;
-+ au_element = AU_IN_BUS;
-+ au_type = "input";
-+ }
-+
-+ uint32_t buffer_frames = 0;
-+ UInt32 size = sizeof(buffer_frames);
-+ int r = AudioUnitGetProperty(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ au_scope,
-+ au_element,
-+ &buffer_frames,
-+ &size);
-+ if (r != noErr) {
-+ if (set_side == INPUT) {
-+ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
-+ } else {
-+ PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
-+ }
-+ return CUBEB_ERROR;
-+ }
-+
-+ if (new_size_frames == buffer_frames) {
-+ LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames);
-+ return CUBEB_OK;
-+ }
-+
-+ r = AudioUnitAddPropertyListener(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ buffer_size_changed_callback,
-+ stm);
-+ if (r != noErr) {
-+ if (set_side == INPUT) {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
-+ } else {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
-+ }
-+ return CUBEB_ERROR;
-+ }
-+
-+ stm->buffer_size_change_state = false;
-+
-+ r = AudioUnitSetProperty(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ au_scope,
-+ au_element,
-+ &new_size_frames,
-+ sizeof(new_size_frames));
-+ if (r != noErr) {
-+ if (set_side == INPUT) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
-+ } else {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
-+ }
-+
-+ r = AudioUnitRemovePropertyListenerWithUserData(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ buffer_size_changed_callback,
-+ stm);
-+ if (r != noErr) {
-+ if (set_side == INPUT) {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
-+ } else {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
-+ }
-+ }
-+
-+ return CUBEB_ERROR;
-+ }
-+
-+ int count = 0;
-+ while (!stm->buffer_size_change_state && count++ < 30) {
-+ struct timespec req, rem;
-+ req.tv_sec = 0;
-+ req.tv_nsec = 100000000L; // 0.1 sec
-+ if (nanosleep(&req , &rem) < 0 ) {
-+ LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec);
-+ }
-+ LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count);
-+ }
-+
-+ r = AudioUnitRemovePropertyListenerWithUserData(au,
-+ kAudioDevicePropertyBufferFrameSize,
-+ buffer_size_changed_callback,
-+ stm);
-+ if (r != noErr) {
-+ return CUBEB_ERROR;
-+ if (set_side == INPUT) {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
-+ } else {
-+ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
-+ }
-+ }
-+
-+ if (!stm->buffer_size_change_state && count >= 30) {
-+ LOG("(%p) Error, did not get buffer size change callback ...", stm);
-+ return CUBEB_ERROR;
-+ }
-+
-+ LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames);
-+ return CUBEB_OK;
-+}
-+
-+static int
-+audiounit_configure_input(cubeb_stream * stm)
-+{
-+ int r = 0;
-+ UInt32 size;
-+ AURenderCallbackStruct aurcbs_in;
-+
-+ LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
-+ stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
-+ stm->input_stream_params.format, stm->latency_frames);
-+
-+ /* Get input device sample rate. */
-+ AudioStreamBasicDescription input_hw_desc;
-+ size = sizeof(AudioStreamBasicDescription);
-+ r = AudioUnitGetProperty(stm->input_unit,
-+ kAudioUnitProperty_StreamFormat,
-+ kAudioUnitScope_Input,
-+ AU_IN_BUS,
-+ &input_hw_desc,
-+ &size);
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
-+ return CUBEB_ERROR;
-+ }
-+ stm->input_hw_rate = input_hw_desc.mSampleRate;
-+ LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
-+
-+ /* Set format description according to the input params. */
-+ r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Setting format description for input failed.", stm);
-+ return r;
-+ }
-+
-+ // Use latency to set buffer size
-+ stm->input_buffer_frames = stm->latency_frames;
-+ r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Error in change input buffer size.", stm);
-+ return CUBEB_ERROR;
-+ }
-+
-+ AudioStreamBasicDescription src_desc = stm->input_desc;
-+ /* Input AudioUnit must be configured with device's sample rate.
-+ we will resample inside input callback. */
-+ src_desc.mSampleRate = stm->input_hw_rate;
-+
-+ r = AudioUnitSetProperty(stm->input_unit,
-+ kAudioUnitProperty_StreamFormat,
-+ kAudioUnitScope_Output,
-+ AU_IN_BUS,
-+ &src_desc,
-+ sizeof(AudioStreamBasicDescription));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
-+ return CUBEB_ERROR;
-+ }
-+
-+ /* Frames per buffer in the input callback. */
-+ r = AudioUnitSetProperty(stm->input_unit,
-+ kAudioUnitProperty_MaximumFramesPerSlice,
-+ kAudioUnitScope_Global,
-+ AU_IN_BUS,
-+ &stm->input_buffer_frames,
-+ sizeof(UInt32));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
-+ return CUBEB_ERROR;
-+ }
-+
-+ // Input only capacity
-+ unsigned int array_capacity = 1;
-+ if (has_output(stm)) {
-+ // Full-duplex increase capacity
-+ array_capacity = 8;
-+ }
-+ if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
-+ return CUBEB_ERROR;
-+ }
-+
-+ assert(stm->input_unit != NULL);
-+ aurcbs_in.inputProc = audiounit_input_callback;
-+ aurcbs_in.inputProcRefCon = stm;
-+
-+ r = AudioUnitSetProperty(stm->input_unit,
-+ kAudioOutputUnitProperty_SetInputCallback,
-+ kAudioUnitScope_Global,
-+ AU_OUT_BUS,
-+ &aurcbs_in,
-+ sizeof(aurcbs_in));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
-+ return CUBEB_ERROR;
-+ }
-+ LOG("(%p) Input audiounit init successfully.", stm);
-+
-+ return CUBEB_OK;
-+}
-+
-+static int
-+audiounit_configure_output(cubeb_stream * stm)
-+{
-+ int r;
-+ AURenderCallbackStruct aurcbs_out;
-+ UInt32 size;
-+
-+
-+ LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
-+ stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
-+ stm->output_stream_params.format, stm->latency_frames);
-+
-+ r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not initialize the audio stream description.", stm);
-+ return r;
-+ }
-+
-+ /* Get output device sample rate. */
-+ AudioStreamBasicDescription output_hw_desc;
-+ size = sizeof(AudioStreamBasicDescription);
-+ memset(&output_hw_desc, 0, size);
-+ r = AudioUnitGetProperty(stm->output_unit,
-+ kAudioUnitProperty_StreamFormat,
-+ kAudioUnitScope_Output,
-+ AU_OUT_BUS,
-+ &output_hw_desc,
-+ &size);
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
-+ return CUBEB_ERROR;
-+ }
-+ stm->output_hw_rate = output_hw_desc.mSampleRate;
-+ LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
-+
-+ r = AudioUnitSetProperty(stm->output_unit,
-+ kAudioUnitProperty_StreamFormat,
-+ kAudioUnitScope_Input,
-+ AU_OUT_BUS,
-+ &stm->output_desc,
-+ sizeof(AudioStreamBasicDescription));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
-+ return CUBEB_ERROR;
-+ }
-+
-+ r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Error in change output buffer size.", stm);
-+ return CUBEB_ERROR;
-+ }
-+
-+ /* Frames per buffer in the input callback. */
-+ r = AudioUnitSetProperty(stm->output_unit,
-+ kAudioUnitProperty_MaximumFramesPerSlice,
-+ kAudioUnitScope_Global,
-+ AU_OUT_BUS,
-+ &stm->latency_frames,
-+ sizeof(UInt32));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r);
-+ return CUBEB_ERROR;
-+ }
-+
-+ assert(stm->output_unit != NULL);
-+ aurcbs_out.inputProc = audiounit_output_callback;
-+ aurcbs_out.inputProcRefCon = stm;
-+ r = AudioUnitSetProperty(stm->output_unit,
-+ kAudioUnitProperty_SetRenderCallback,
-+ kAudioUnitScope_Global,
-+ AU_OUT_BUS,
-+ &aurcbs_out,
-+ sizeof(aurcbs_out));
-+ if (r != noErr) {
-+ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
-+ return CUBEB_ERROR;
-+ }
-+
-+ LOG("(%p) Output audiounit init successfully.", stm);
-+ return CUBEB_OK;
-+}
-+
-+static int
-+audiounit_setup_stream(cubeb_stream * stm)
- {
- stm->mutex.assert_current_thread_owns();
-
-- int r;
-- AURenderCallbackStruct aurcbs_in;
-- AURenderCallbackStruct aurcbs_out;
-- UInt32 size;
--
-+ int r = 0;
- if (has_input(stm)) {
- r = audiounit_create_unit(&stm->input_unit, true,
- &stm->input_stream_params,
- stm->input_device);
- if (r != CUBEB_OK) {
- LOG("(%p) AudioUnit creation for input failed.", stm);
- return r;
- }
-@@ -1249,180 +1622,46 @@ setup_audiounit_stream(cubeb_stream * st
- &stm->output_stream_params,
- stm->output_device);
- if (r != CUBEB_OK) {
- LOG("(%p) AudioUnit creation for output failed.", stm);
- return r;
- }
- }
-
-+ /* Latency cannot change if another stream is operating in parallel. In this case
-+ * latecy is set to the other stream value. */
-+ if (stm->context->active_streams > 1) {
-+ LOG("(%p) More than one active stream, use global latency.", stm);
-+ stm->latency_frames = stm->context->global_latency_frames;
-+ } else {
-+ /* Silently clamp the latency down to the platform default, because we
-+ * synthetize the clock from the callbacks, and we want the clock to update
-+ * often. */
-+ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames);
-+ assert(stm->latency_frames); // Ungly error check
-+ audiounit_set_global_latency(stm, stm->latency_frames);
-+ }
-+
- /* Setup Input Stream! */
- if (has_input(stm)) {
-- LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
-- stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
-- stm->input_stream_params.format, stm->latency_frames);
-- /* Get input device sample rate. */
-- AudioStreamBasicDescription input_hw_desc;
-- size = sizeof(AudioStreamBasicDescription);
-- r = AudioUnitGetProperty(stm->input_unit,
-- kAudioUnitProperty_StreamFormat,
-- kAudioUnitScope_Input,
-- AU_IN_BUS,
-- &input_hw_desc,
-- &size);
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
-- return CUBEB_ERROR;
-- }
-- stm->input_hw_rate = input_hw_desc.mSampleRate;
-- LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
--
-- /* Set format description according to the input params. */
-- r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
-+ r = audiounit_configure_input(stm);
- if (r != CUBEB_OK) {
-- LOG("(%p) Setting format description for input failed.", stm);
-+ LOG("(%p) Configure audiounit input failed.", stm);
- return r;
- }
--
-- // Use latency to set buffer size
-- stm->input_buffer_frames = stm->latency_frames;
-- LOG("(%p) Input buffer frame count %u.", stm, unsigned(stm->input_buffer_frames));
-- r = AudioUnitSetProperty(stm->input_unit,
-- kAudioDevicePropertyBufferFrameSize,
-- kAudioUnitScope_Output,
-- AU_IN_BUS,
-- &stm->input_buffer_frames,
-- sizeof(UInt32));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
-- return CUBEB_ERROR;
-- }
--
-- AudioStreamBasicDescription src_desc = stm->input_desc;
-- /* Input AudioUnit must be configured with device's sample rate.
-- we will resample inside input callback. */
-- src_desc.mSampleRate = stm->input_hw_rate;
--
-- r = AudioUnitSetProperty(stm->input_unit,
-- kAudioUnitProperty_StreamFormat,
-- kAudioUnitScope_Output,
-- AU_IN_BUS,
-- &src_desc,
-- sizeof(AudioStreamBasicDescription));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
-- return CUBEB_ERROR;
-- }
--
-- /* Frames per buffer in the input callback. */
-- r = AudioUnitSetProperty(stm->input_unit,
-- kAudioUnitProperty_MaximumFramesPerSlice,
-- kAudioUnitScope_Output,
-- AU_IN_BUS,
-- &stm->input_buffer_frames,
-- sizeof(UInt32));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
-- return CUBEB_ERROR;
-- }
--
-- // Input only capacity
-- unsigned int array_capacity = 1;
-- if (has_output(stm)) {
-- // Full-duplex increase capacity
-- array_capacity = 8;
-- }
-- if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
-- return CUBEB_ERROR;
-- }
--
-- assert(stm->input_unit != NULL);
-- aurcbs_in.inputProc = audiounit_input_callback;
-- aurcbs_in.inputProcRefCon = stm;
--
-- r = AudioUnitSetProperty(stm->input_unit,
-- kAudioOutputUnitProperty_SetInputCallback,
-- kAudioUnitScope_Global,
-- AU_OUT_BUS,
-- &aurcbs_in,
-- sizeof(aurcbs_in));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
-- return CUBEB_ERROR;
-- }
-- LOG("(%p) Input audiounit init successfully.", stm);
- }
-
- /* Setup Output Stream! */
- if (has_output(stm)) {
-- LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
-- stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
-- stm->output_stream_params.format, stm->latency_frames);
-- r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
-+ r = audiounit_configure_output(stm);
- if (r != CUBEB_OK) {
-- LOG("(%p) Could not initialize the audio stream description.", stm);
-+ LOG("(%p) Configure audiounit output failed.", stm);
- return r;
- }
--
-- /* Get output device sample rate. */
-- AudioStreamBasicDescription output_hw_desc;
-- size = sizeof(AudioStreamBasicDescription);
-- memset(&output_hw_desc, 0, size);
-- r = AudioUnitGetProperty(stm->output_unit,
-- kAudioUnitProperty_StreamFormat,
-- kAudioUnitScope_Output,
-- AU_OUT_BUS,
-- &output_hw_desc,
-- &size);
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
-- return CUBEB_ERROR;
-- }
-- stm->output_hw_rate = output_hw_desc.mSampleRate;
-- LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
--
-- r = AudioUnitSetProperty(stm->output_unit,
-- kAudioUnitProperty_StreamFormat,
-- kAudioUnitScope_Input,
-- AU_OUT_BUS,
-- &stm->output_desc,
-- sizeof(AudioStreamBasicDescription));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
-- return CUBEB_ERROR;
-- }
--
-- // Use latency to calculate buffer size
-- uint32_t output_buffer_frames = stm->latency_frames;
-- LOG("(%p) Output buffer frame count %u.", stm, output_buffer_frames);
-- r = AudioUnitSetProperty(stm->output_unit,
-- kAudioDevicePropertyBufferFrameSize,
-- kAudioUnitScope_Input,
-- AU_OUT_BUS,
-- &output_buffer_frames,
-- sizeof(output_buffer_frames));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
-- return CUBEB_ERROR;
-- }
--
-- assert(stm->output_unit != NULL);
-- aurcbs_out.inputProc = audiounit_output_callback;
-- aurcbs_out.inputProcRefCon = stm;
-- r = AudioUnitSetProperty(stm->output_unit,
-- kAudioUnitProperty_SetRenderCallback,
-- kAudioUnitScope_Global,
-- AU_OUT_BUS,
-- &aurcbs_out,
-- sizeof(aurcbs_out));
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
-- return CUBEB_ERROR;
-- }
-- LOG("(%p) Output audiounit init successfully.", stm);
- }
-
- // Setting the latency doesn't work well for USB headsets (eg. plantronics).
- // Keep the default latency for now.
- #if 0
- buffer_size = latency;
-
- /* Get the range of latency this particular device can work with, and clamp
-@@ -1535,63 +1774,60 @@ audiounit_stream_init(cubeb * context,
- void * user_ptr)
- {
- cubeb_stream * stm;
- int r;
-
- assert(context);
- *stream = NULL;
-
-+ assert(latency_frames > 0);
- if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) {
- LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX);
- return CUBEB_ERROR;
- }
-- context->active_streams += 1;
-
- stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream));
- assert(stm);
- // Placement new to call the ctors of cubeb_stream members.
- new (stm) cubeb_stream();
-
- /* These could be different in the future if we have both
- * full-duplex stream and different devices for input vs output. */
- stm->context = context;
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
-+ stm->latency_frames = latency_frames;
- stm->device_changed_callback = NULL;
- if (input_stream_params) {
- stm->input_stream_params = *input_stream_params;
- stm->input_device = input_device;
- stm->is_default_input = input_device == nullptr ||
- (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) ==
- reinterpret_cast<intptr_t>(input_device));
- }
- if (output_stream_params) {
- stm->output_stream_params = *output_stream_params;
- stm->output_device = output_device;
- }
-
- /* Init data members where necessary */
- stm->hw_latency_frames = UINT64_MAX;
-
-- /* Silently clamp the latency down to the platform default, because we
-- * synthetize the clock from the callbacks, and we want the clock to update
-- * often. */
-- stm->latency_frames = audiounit_clamp_latency(stm, latency_frames);
-- assert(latency_frames > 0);
--
- stm->switching_device = false;
-
-+ auto_lock context_lock(context->mutex);
- {
- // It's not critical to lock here, because no other thread has been started
- // yet, but it allows to assert that the lock has been taken in
-- // `setup_audiounit_stream`.
-+ // `audiounit_setup_stream`.
-+ context->active_streams += 1;
- auto_lock lock(stm->mutex);
-- r = setup_audiounit_stream(stm);
-+ r = audiounit_setup_stream(stm);
- }
-
- if (r != CUBEB_OK) {
- LOG("(%p) Could not setup the audiounit stream.", stm);
- audiounit_stream_destroy(stm);
- return r;
- }
-
-@@ -1602,17 +1838,17 @@ audiounit_stream_init(cubeb * context,
- }
-
- *stream = stm;
- LOG("Cubeb stream (%p) init successful.", stm);
- return CUBEB_OK;
- }
-
- static void
--close_audiounit_stream(cubeb_stream * stm)
-+audiounit_close_stream(cubeb_stream *stm)
- {
- stm->mutex.assert_current_thread_owns();
- if (stm->input_unit) {
- AudioUnitUninitialize(stm->input_unit);
- AudioComponentInstanceDispose(stm->input_unit);
- }
-
- audiounit_destroy_input_linear_buffer(stm);
-@@ -1625,33 +1861,36 @@ close_audiounit_stream(cubeb_stream * st
- cubeb_resampler_destroy(stm->resampler);
- }
-
- static void
- audiounit_stream_destroy(cubeb_stream * stm)
- {
- stm->shutdown = true;
-
-+ auto_lock context_locl(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- {
- auto_lock lock(stm->mutex);
-- close_audiounit_stream(stm);
-+ audiounit_close_stream(stm);
- }
-
- #if !TARGET_OS_IPHONE
- int r = audiounit_uninstall_device_changed_callback(stm);
- if (r != CUBEB_OK) {
- LOG("(%p) Could not uninstall the device changed callback", stm);
- }
- #endif
-
- assert(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
-
-+ LOG("Cubeb stream (%p) destroyed successful.", stm);
-+
- stm->~cubeb_stream();
- free(stm);
- }
-
- void
- audiounit_stream_start_internal(cubeb_stream * stm)
- {
- OSStatus r;
-@@ -1666,16 +1905,17 @@ audiounit_stream_start_internal(cubeb_st
- }
-
- static int
- audiounit_stream_start(cubeb_stream * stm)
- {
- stm->shutdown = false;
- stm->draining = false;
-
-+ auto_lock context_locl(stm->context->mutex);
- audiounit_stream_start_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
-
- LOG("Cubeb stream (%p) started successfully.", stm);
- return CUBEB_OK;
- }
-
-@@ -1693,16 +1933,17 @@ audiounit_stream_stop_internal(cubeb_str
- }
- }
-
- static int
- audiounit_stream_stop(cubeb_stream * stm)
- {
- stm->shutdown = true;
-
-+ auto_lock context_locl(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-
- LOG("Cubeb stream (%p) stopped successfully.", stm);
- return CUBEB_OK;
- }
-
diff --git a/media/libcubeb/prevent-double-free.patch b/media/libcubeb/prevent-double-free.patch
deleted file mode 100644
index aa5356d7f2..0000000000
--- a/media/libcubeb/prevent-double-free.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From f82f15635e09aac4f07d2ddac3d53c84b593d911 Mon Sep 17 00:00:00 2001
-From: Paul Adenot <paul@paul.cx>
-Date: Mon, 16 Jan 2017 04:49:41 -0800
-Subject: [PATCH 1/1] Prevent double-free when doing an emergency bailout from
- the rendering thread.
-
-This caused gecko bug 1326176.
-
-This was caused by the fact that we would null out `stm->thread` when in
-fact it was still running, so we would delete `stm->emergency_bailout`
-twice, because we would return true from `stop_and_join_thread`.
----
- src/cubeb_wasapi.cpp | 15 ++++++++++-----
- 1 file changed, 10 insertions(+), 5 deletions(-)
-
-diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp
-index 63c12ac..2920b5d 100644
---- a/src/cubeb_wasapi.cpp
-+++ b/src/cubeb_wasapi.cpp
-@@ -1230,13 +1230,18 @@ bool stop_and_join_render_thread(cubeb_stream * stm)
- rv = false;
- }
-
-- LOG("Closing thread.");
-
-- CloseHandle(stm->thread);
-- stm->thread = NULL;
-+ // Only attempts to close and null out the thread and event if the
-+ // WaitForSingleObject above succeeded, so that calling this function again
-+ // attemps to clean up the thread and event each time.
-+ if (rv) {
-+ LOG("Closing thread.");
-+ CloseHandle(stm->thread);
-+ stm->thread = NULL;
-
-- CloseHandle(stm->shutdown_event);
-- stm->shutdown_event = 0;
-+ CloseHandle(stm->shutdown_event);
-+ stm->shutdown_event = 0;
-+ }
-
- return rv;
- }
---
-2.7.4
-
diff --git a/media/libcubeb/src/android/audiotrack_definitions.h b/media/libcubeb/src/android/audiotrack_definitions.h
deleted file mode 100644
index cd501533d9..0000000000
--- a/media/libcubeb/src/android/audiotrack_definitions.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdint.h>
-
-/*
- * The following definitions are copied from the android sources. Only the
- * relevant enum member and values needed are copied.
- */
-
-/*
- * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
- */
-typedef int32_t status_t;
-
-/*
- * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h
- */
-struct Buffer {
- uint32_t flags;
- int channelCount;
- int format;
- size_t frameCount;
- size_t size;
- union {
- void* raw;
- short* i16;
- int8_t* i8;
- };
-};
-
-enum event_type {
- EVENT_MORE_DATA = 0,
- EVENT_UNDERRUN = 1,
- EVENT_LOOP_END = 2,
- EVENT_MARKER = 3,
- EVENT_NEW_POS = 4,
- EVENT_BUFFER_END = 5
-};
-
-/**
- * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioSystem.h
- * and
- * https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h
- */
-
-#define AUDIO_STREAM_TYPE_MUSIC 3
-
-enum {
- AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1,
- AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2,
- AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS,
- AUDIO_CHANNEL_OUT_STEREO_ICS = (AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS)
-} AudioTrack_ChannelMapping_ICS;
-
-enum {
- AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy = 0x4,
- AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy = 0x8,
- AUDIO_CHANNEL_OUT_MONO_Legacy = AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy,
- AUDIO_CHANNEL_OUT_STEREO_Legacy = (AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy | AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy)
-} AudioTrack_ChannelMapping_Legacy;
-
-typedef enum {
- AUDIO_FORMAT_PCM = 0x00000000,
- AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
- AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
-} AudioTrack_SampleType;
-
diff --git a/media/libcubeb/src/android/cubeb-output-latency.h b/media/libcubeb/src/android/cubeb-output-latency.h
new file mode 100644
index 0000000000..870a884a3c
--- /dev/null
+++ b/media/libcubeb/src/android/cubeb-output-latency.h
@@ -0,0 +1,76 @@
+#ifndef _CUBEB_OUTPUT_LATENCY_H_
+#define _CUBEB_OUTPUT_LATENCY_H_
+
+#include "../cubeb-jni.h"
+#include "cubeb_media_library.h"
+#include <stdbool.h>
+
+struct output_latency_function {
+ media_lib * from_lib;
+ cubeb_jni * from_jni;
+ int version;
+};
+
+typedef struct output_latency_function output_latency_function;
+
+const int ANDROID_JELLY_BEAN_MR1_4_2 = 17;
+
+output_latency_function *
+cubeb_output_latency_load_method(int version)
+{
+ output_latency_function * ol = NULL;
+ ol = calloc(1, sizeof(output_latency_function));
+
+ ol->version = version;
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
+ ol->from_jni = cubeb_jni_init();
+ return ol;
+ }
+
+ ol->from_lib = cubeb_load_media_library();
+ return ol;
+}
+
+bool
+cubeb_output_latency_method_is_loaded(output_latency_function * ol)
+{
+ assert(ol);
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
+ return !!ol->from_jni;
+ }
+
+ return !!ol->from_lib;
+}
+
+void
+cubeb_output_latency_unload_method(output_latency_function * ol)
+{
+ if (!ol) {
+ return;
+ }
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) {
+ cubeb_jni_destroy(ol->from_jni);
+ }
+
+ if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) {
+ cubeb_close_media_library(ol->from_lib);
+ }
+
+ free(ol);
+}
+
+uint32_t
+cubeb_get_output_latency(output_latency_function * ol)
+{
+ assert(cubeb_output_latency_method_is_loaded(ol));
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
+ return cubeb_get_output_latency_from_jni(ol->from_jni);
+ }
+
+ return cubeb_get_output_latency_from_media_library(ol->from_lib);
+}
+
+#endif // _CUBEB_OUTPUT_LATENCY_H_
diff --git a/media/libcubeb/src/android/cubeb_media_library.h b/media/libcubeb/src/android/cubeb_media_library.h
new file mode 100644
index 0000000000..27fbc86ec2
--- /dev/null
+++ b/media/libcubeb/src/android/cubeb_media_library.h
@@ -0,0 +1,64 @@
+#ifndef _CUBEB_MEDIA_LIBRARY_H_
+#define _CUBEB_MEDIA_LIBRARY_H_
+
+struct media_lib {
+ void * libmedia;
+ int32_t (*get_output_latency)(uint32_t * latency, int stream_type);
+};
+
+typedef struct media_lib media_lib;
+
+media_lib *
+cubeb_load_media_library()
+{
+ media_lib ml = {0};
+ ml.libmedia = dlopen("libmedia.so", RTLD_LAZY);
+ if (!ml.libmedia) {
+ return NULL;
+ }
+
+ // Get the latency, in ms, from AudioFlinger. First, try the most recent
+ // signature. status_t AudioSystem::getOutputLatency(uint32_t* latency,
+ // audio_stream_type_t streamType)
+ ml.get_output_latency = dlsym(
+ ml.libmedia,
+ "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
+ if (!ml.get_output_latency) {
+ // In case of failure, try the signature from legacy version.
+ // status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType)
+ ml.get_output_latency =
+ dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
+ if (!ml.get_output_latency) {
+ return NULL;
+ }
+ }
+
+ media_lib * rv = NULL;
+ rv = calloc(1, sizeof(media_lib));
+ assert(rv);
+ *rv = ml;
+ return rv;
+}
+
+void
+cubeb_close_media_library(media_lib * ml)
+{
+ dlclose(ml->libmedia);
+ ml->libmedia = NULL;
+ ml->get_output_latency = NULL;
+ free(ml);
+}
+
+uint32_t
+cubeb_get_output_latency_from_media_library(media_lib * ml)
+{
+ uint32_t latency = 0;
+ const int audio_stream_type_music = 3;
+ int32_t r = ml->get_output_latency(&latency, audio_stream_type_music);
+ if (r) {
+ return 0;
+ }
+ return latency;
+}
+
+#endif // _CUBEB_MEDIA_LIBRARY_H_
diff --git a/media/libcubeb/src/android/sles_definitions.h b/media/libcubeb/src/android/sles_definitions.h
index 1b1ace567e..b107003d1b 100644
--- a/media/libcubeb/src/android/sles_definitions.h
+++ b/media/libcubeb/src/android/sles_definitions.h
@@ -29,24 +29,23 @@
/** Audio recording preset */
/** Audio recording preset key */
-#define SL_ANDROID_KEY_RECORDING_PRESET ((const SLchar*) "androidRecordingPreset")
+#define SL_ANDROID_KEY_RECORDING_PRESET \
+ ((const SLchar *)"androidRecordingPreset")
/** Audio recording preset values */
/** preset "none" cannot be set, it is used to indicate the current settings
* do not match any of the presets. */
-#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32) 0x00000000)
+#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32)0x00000000)
/** generic recording configuration on the platform */
-#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32) 0x00000001)
+#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32)0x00000001)
/** uses the microphone audio source with the same orientation as the camera
* if available, the main device microphone otherwise */
-#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32) 0x00000002)
+#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32)0x00000002)
/** uses the main microphone tuned for voice recognition */
-#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32) 0x00000003)
+#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32)0x00000003)
/** uses the main microphone tuned for audio communications */
-#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004)
-
-/** Audio recording get session ID (read only) */
-/** Audio recording get session ID key */
-#define SL_ANDROID_KEY_RECORDING_SESSION_ID ((const SLchar*) "androidRecordingSessionId")
+#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32)0x00000004)
+/** uses the main microphone unprocessed */
+#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32)0x00000005)
/*---------------------------------------------------------------------------*/
/* Android AudioPlayer configuration */
@@ -54,24 +53,52 @@
/** Audio playback stream type */
/** Audio playback stream type key */
-#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar*) "androidPlaybackStreamType")
+#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar *)"androidPlaybackStreamType")
/** Audio playback stream type values */
/* same as android.media.AudioManager.STREAM_VOICE_CALL */
-#define SL_ANDROID_STREAM_VOICE ((SLint32) 0x00000000)
+#define SL_ANDROID_STREAM_VOICE ((SLint32)0x00000000)
/* same as android.media.AudioManager.STREAM_SYSTEM */
-#define SL_ANDROID_STREAM_SYSTEM ((SLint32) 0x00000001)
+#define SL_ANDROID_STREAM_SYSTEM ((SLint32)0x00000001)
/* same as android.media.AudioManager.STREAM_RING */
-#define SL_ANDROID_STREAM_RING ((SLint32) 0x00000002)
+#define SL_ANDROID_STREAM_RING ((SLint32)0x00000002)
/* same as android.media.AudioManager.STREAM_MUSIC */
-#define SL_ANDROID_STREAM_MEDIA ((SLint32) 0x00000003)
+#define SL_ANDROID_STREAM_MEDIA ((SLint32)0x00000003)
/* same as android.media.AudioManager.STREAM_ALARM */
-#define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004)
+#define SL_ANDROID_STREAM_ALARM ((SLint32)0x00000004)
/* same as android.media.AudioManager.STREAM_NOTIFICATION */
-#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005)
-/* same as android.media.AudioManager.STREAM_BLUETOOTH_SCO */
-#define SL_ANDROID_STREAM_BLUETOOTH_SCO ((SLint32) 0x00000006)
-/* same as android.media.AudioManager.STREAM_SYSTEM_ENFORCED */
-#define SL_ANDROID_STREAM_SYSTEM_ENFORCED ((SLint32) 0x00000007)
+#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32)0x00000005)
+
+/*---------------------------------------------------------------------------*/
+/* Android AudioPlayer and AudioRecorder configuration */
+/*---------------------------------------------------------------------------*/
+
+/** Audio Performance mode.
+ * Performance mode tells the framework how to configure the audio path
+ * for a player or recorder according to application performance and
+ * functional requirements.
+ * It affects the output or input latency based on acceptable tradeoffs on
+ * battery drain and use of pre or post processing effects.
+ * Performance mode should be set before realizing the object and should be
+ * read after realizing the object to check if the requested mode could be
+ * granted or not.
+ */
+/** Audio Performance mode key */
+#define SL_ANDROID_KEY_PERFORMANCE_MODE \
+ ((const SLchar *)"androidPerformanceMode")
+
+/** Audio performance values */
+/* No specific performance requirement. Allows HW and SW pre/post
+ * processing. */
+#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32)0x00000000)
+/* Priority given to latency. No HW or software pre/post processing.
+ * This is the default if no performance mode is specified. */
+#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32)0x00000001)
+/* Priority given to latency while still allowing HW pre and post
+ * processing. */
+#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32)0x00000002)
+/* Priority given to power saving if latency is not a concern.
+ * Allows HW and SW pre/post processing. */
+#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32)0x00000003)
#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */
diff --git a/media/libcubeb/src/audiotrack_definitions.h b/media/libcubeb/src/audiotrack_definitions.h
deleted file mode 100644
index 2beeca2de7..0000000000
--- a/media/libcubeb/src/audiotrack_definitions.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cubeb/cubeb-stdint.h>
-
-/*
- * The following definitions are copied from the android sources. Only the
- * relevant enum member and values needed are copied.
- */
-
-/*
- * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
- */
-typedef int32_t status_t;
-
-/*
- * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h
- */
-struct Buffer {
- uint32_t flags;
- int channelCount;
- int format;
- size_t frameCount;
- size_t size;
- union {
- void* raw;
- short* i16;
- int8_t* i8;
- };
-};
-
-enum event_type {
- EVENT_MORE_DATA = 0,
- EVENT_UNDERRUN = 1,
- EVENT_LOOP_END = 2,
- EVENT_MARKER = 3,
- EVENT_NEW_POS = 4,
- EVENT_BUFFER_END = 5
-};
-
-/**
- * From https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h
- */
-
-#define AUDIO_STREAM_TYPE_MUSIC 3
-
-enum {
- AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1,
- AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2,
- AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS,
- AUDIO_CHANNEL_OUT_STEREO_ICS = (AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS)
-} AudioTrack_ChannelMapping_ICS;
-
-typedef enum {
- AUDIO_FORMAT_PCM = 0x00000000,
- AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
- AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
-} AudioTrack_SampleType;
-
diff --git a/media/libcubeb/src/cubeb-internal.h b/media/libcubeb/src/cubeb-internal.h
index dfcc186c57..79326e1ebf 100644
--- a/media/libcubeb/src/cubeb-internal.h
+++ b/media/libcubeb/src/cubeb-internal.h
@@ -8,6 +8,7 @@
#define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5
#include "cubeb/cubeb.h"
+#include "cubeb_assert.h"
#include "cubeb_log.h"
#include <stdio.h>
#include <string.h>
@@ -20,7 +21,7 @@
#define CLANG_ANALYZER_NORETURN
#endif // ifndef CLANG_ANALYZER_NORETURN
#endif // __has_feature(attribute_analyzer_noreturn)
-#else // __clang__
+#else // __clang__
#define CLANG_ANALYZER_NORETURN
#endif
@@ -28,59 +29,46 @@
extern "C" {
#endif
-/* Crash the caller. */
-void cubeb_crash() CLANG_ANALYZER_NORETURN;
-
#if defined(__cplusplus)
}
#endif
struct cubeb_ops {
- int (* init)(cubeb ** context, char const * context_name);
- char const * (* get_backend_id)(cubeb * context);
- int (* get_max_channel_count)(cubeb * context, uint32_t * max_channels);
- int (* get_min_latency)(cubeb * context,
- cubeb_stream_params params,
- uint32_t * latency_ms);
- int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
- int (* enumerate_devices)(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection);
- void (* destroy)(cubeb * context);
- int (* stream_init)(cubeb * context,
- cubeb_stream ** stream,
- char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr);
- void (* stream_destroy)(cubeb_stream * stream);
- int (* stream_start)(cubeb_stream * stream);
- int (* stream_stop)(cubeb_stream * stream);
- int (* stream_get_position)(cubeb_stream * stream, uint64_t * position);
- int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
- int (* stream_set_volume)(cubeb_stream * stream, float volumes);
- int (* stream_set_panning)(cubeb_stream * stream, float panning);
- int (* stream_get_current_device)(cubeb_stream * stream,
- cubeb_device ** const device);
- int (* stream_device_destroy)(cubeb_stream * stream,
- cubeb_device * device);
- int (* stream_register_device_changed_callback)(cubeb_stream * stream,
- cubeb_device_changed_callback device_changed_callback);
- int (* register_device_collection_changed)(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback callback,
- void * user_ptr);
+ int (*init)(cubeb ** context, char const * context_name);
+ char const * (*get_backend_id)(cubeb * context);
+ int (*get_max_channel_count)(cubeb * context, uint32_t * max_channels);
+ int (*get_min_latency)(cubeb * context, cubeb_stream_params params,
+ uint32_t * latency_ms);
+ int (*get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
+ int (*enumerate_devices)(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection);
+ int (*device_collection_destroy)(cubeb * context,
+ cubeb_device_collection * collection);
+ void (*destroy)(cubeb * context);
+ int (*stream_init)(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr);
+ void (*stream_destroy)(cubeb_stream * stream);
+ int (*stream_start)(cubeb_stream * stream);
+ int (*stream_stop)(cubeb_stream * stream);
+ int (*stream_get_position)(cubeb_stream * stream, uint64_t * position);
+ int (*stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
+ int (*stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency);
+ int (*stream_set_volume)(cubeb_stream * stream, float volumes);
+ int (*stream_set_name)(cubeb_stream * stream, char const * stream_name);
+ int (*stream_get_current_device)(cubeb_stream * stream,
+ cubeb_device ** const device);
+ int (*stream_device_destroy)(cubeb_stream * stream, cubeb_device * device);
+ int (*stream_register_device_changed_callback)(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback);
+ int (*register_device_collection_changed)(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback callback, void * user_ptr);
};
-#define XASSERT(expr) do { \
- if (!(expr)) { \
- fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
- cubeb_crash(); \
- } \
- } while (0)
-
#endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */
diff --git a/media/libcubeb/src/cubeb-jni.cpp b/media/libcubeb/src/cubeb-jni.cpp
new file mode 100644
index 0000000000..d50326606b
--- /dev/null
+++ b/media/libcubeb/src/cubeb-jni.cpp
@@ -0,0 +1,80 @@
+#include "cubeb-jni-instances.h"
+#include "jni.h"
+#include <assert.h>
+
+#define AUDIO_STREAM_TYPE_MUSIC 3
+
+struct cubeb_jni {
+ jobject s_audio_manager_obj = nullptr;
+ jclass s_audio_manager_class = nullptr;
+ jmethodID s_get_output_latency_id = nullptr;
+};
+
+extern "C" cubeb_jni *
+cubeb_jni_init()
+{
+ jobject ctx_obj = cubeb_jni_get_context_instance();
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ if (!jni_env || !ctx_obj) {
+ return nullptr;
+ }
+
+ cubeb_jni * cubeb_jni_ptr = new cubeb_jni;
+ assert(cubeb_jni_ptr);
+
+ // Find the audio manager object and make it global to call it from another
+ // method
+ jclass context_class = jni_env->FindClass("android/content/Context");
+ jfieldID audio_service_field = jni_env->GetStaticFieldID(
+ context_class, "AUDIO_SERVICE", "Ljava/lang/String;");
+ jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class,
+ audio_service_field);
+ jmethodID get_system_service_id =
+ jni_env->GetMethodID(context_class, "getSystemService",
+ "(Ljava/lang/String;)Ljava/lang/Object;");
+ jobject audio_manager_obj =
+ jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr);
+ cubeb_jni_ptr->s_audio_manager_obj =
+ reinterpret_cast<jobject>(jni_env->NewGlobalRef(audio_manager_obj));
+
+ // Make the audio manager class a global reference in order to preserve method
+ // id
+ jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager");
+ cubeb_jni_ptr->s_audio_manager_class =
+ reinterpret_cast<jclass>(jni_env->NewGlobalRef(audio_manager_class));
+ cubeb_jni_ptr->s_get_output_latency_id =
+ jni_env->GetMethodID(audio_manager_class, "getOutputLatency", "(I)I");
+
+ jni_env->DeleteLocalRef(ctx_obj);
+ jni_env->DeleteLocalRef(context_class);
+ jni_env->DeleteLocalRef(jstr);
+ jni_env->DeleteLocalRef(audio_manager_obj);
+ jni_env->DeleteLocalRef(audio_manager_class);
+
+ return cubeb_jni_ptr;
+}
+
+extern "C" int
+cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr)
+{
+ assert(cubeb_jni_ptr);
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ return jni_env->CallIntMethod(
+ cubeb_jni_ptr->s_audio_manager_obj,
+ cubeb_jni_ptr->s_get_output_latency_id,
+ AUDIO_STREAM_TYPE_MUSIC); // param: AudioManager.STREAM_MUSIC
+}
+
+extern "C" void
+cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr)
+{
+ assert(cubeb_jni_ptr);
+
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ assert(jni_env);
+
+ jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_obj);
+ jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_class);
+
+ delete cubeb_jni_ptr;
+}
diff --git a/media/libcubeb/src/cubeb-jni.h b/media/libcubeb/src/cubeb-jni.h
new file mode 100644
index 0000000000..c4a712a062
--- /dev/null
+++ b/media/libcubeb/src/cubeb-jni.h
@@ -0,0 +1,13 @@
+#ifndef _CUBEB_JNI_H_
+#define _CUBEB_JNI_H_
+
+typedef struct cubeb_jni cubeb_jni;
+
+cubeb_jni *
+cubeb_jni_init();
+int
+cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr);
+void
+cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr);
+
+#endif // _CUBEB_JNI_H_
diff --git a/media/libcubeb/src/cubeb-sles.h b/media/libcubeb/src/cubeb-sles.h
deleted file mode 100644
index 0725523528..0000000000
--- a/media/libcubeb/src/cubeb-sles.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef _CUBEB_SLES_H_
-#define _CUBEB_SLES_H_
-#include <OpenSLESProvider.h>
-#include <SLES/OpenSLES.h>
-
-static SLresult cubeb_get_sles_engine(
- SLObjectItf *pEngine,
- SLuint32 numOptions,
- const SLEngineOption *pEngineOptions,
- SLuint32 numInterfaces,
- const SLInterfaceID *pInterfaceIds,
- const SLboolean * pInterfaceRequired) {
- return mozilla_get_sles_engine(pEngine, numOptions, pEngineOptions);
-}
-
-static void cubeb_destroy_sles_engine(SLObjectItf *self) {
- mozilla_destroy_sles_engine(self);
-}
-
-/* Only synchronous operation is supported, as if the second
- parameter was FALSE. */
-static SLresult cubeb_realize_sles_engine(SLObjectItf self) {
- return mozilla_realize_sles_engine(self);
-}
-
-#endif
diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c
index a239319a46..b3d32eea3d 100644
--- a/media/libcubeb/src/cubeb.c
+++ b/media/libcubeb/src/cubeb.c
@@ -5,88 +5,121 @@
* accompanying file LICENSE for details.
*/
#undef NDEBUG
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
+#include <string.h>
-#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
-
-cubeb_log_level g_log_level;
-cubeb_log_callback g_log_callback;
+#define NELEMS(x) ((int)(sizeof(x) / sizeof(x[0])))
struct cubeb {
struct cubeb_ops * ops;
};
struct cubeb_stream {
+ /*
+ * Note: All implementations of cubeb_stream must keep the following
+ * layout.
+ */
struct cubeb * context;
+ void * user_ptr;
};
#if defined(USE_PULSE)
-int pulse_init(cubeb ** context, char const * context_name);
+int
+pulse_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_PULSE_RUST)
+int
+pulse_rust_init(cubeb ** contet, char const * context_name);
#endif
#if defined(USE_JACK)
-int jack_init (cubeb ** context, char const * context_name);
+int
+jack_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_ALSA)
-int alsa_init(cubeb ** context, char const * context_name);
+int
+alsa_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_AUDIOUNIT)
-int audiounit_init(cubeb ** context, char const * context_name);
+int
+audiounit_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_AUDIOUNIT_RUST)
+int
+audiounit_rust_init(cubeb ** contet, char const * context_name);
#endif
#if defined(USE_WINMM)
-int winmm_init(cubeb ** context, char const * context_name);
+int
+winmm_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_WASAPI)
-int wasapi_init(cubeb ** context, char const * context_name);
+int
+wasapi_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_SNDIO)
-int sndio_init(cubeb ** context, char const * context_name);
+int
+sndio_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_SUN)
+int
+sun_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_OPENSL)
-int opensl_init(cubeb ** context, char const * context_name);
+int
+opensl_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_OSS)
+int
+oss_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_AAUDIO)
+int
+aaudio_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_AUDIOTRACK)
-int audiotrack_init(cubeb ** context, char const * context_name);
+int
+audiotrack_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_KAI)
-int kai_init(cubeb ** context, char const * context_name);
-#endif
-#if defined(USE_SUN)
-int sunaudio_init(cubeb ** context, char const * context_name);
+int
+kai_init(cubeb ** context, char const * context_name);
#endif
-
static int
validate_stream_params(cubeb_stream_params * input_stream_params,
cubeb_stream_params * output_stream_params)
{
XASSERT(input_stream_params || output_stream_params);
if (output_stream_params) {
- if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 ||
- output_stream_params->channels < 1 || output_stream_params->channels > 8) {
+ if (output_stream_params->rate < 1000 ||
+ output_stream_params->rate > 192000 ||
+ output_stream_params->channels < 1 ||
+ output_stream_params->channels > UINT8_MAX) {
return CUBEB_ERROR_INVALID_FORMAT;
}
}
if (input_stream_params) {
- if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 ||
- input_stream_params->channels < 1 || input_stream_params->channels > 8) {
+ if (input_stream_params->rate < 1000 ||
+ input_stream_params->rate > 192000 ||
+ input_stream_params->channels < 1 ||
+ input_stream_params->channels > UINT8_MAX) {
return CUBEB_ERROR_INVALID_FORMAT;
}
}
// Rate and sample format must be the same for input and output, if using a
// duplex stream
if (input_stream_params && output_stream_params) {
- if (input_stream_params->rate != output_stream_params->rate ||
+ if (input_stream_params->rate != output_stream_params->rate ||
input_stream_params->format != output_stream_params->format) {
return CUBEB_ERROR_INVALID_FORMAT;
}
}
- cubeb_stream_params * params = input_stream_params ?
- input_stream_params : output_stream_params;
+ cubeb_stream_params * params =
+ input_stream_params ? input_stream_params : output_stream_params;
switch (params->format) {
case CUBEB_SAMPLE_S16LE:
@@ -99,8 +132,6 @@ validate_stream_params(cubeb_stream_params * input_stream_params,
return CUBEB_ERROR_INVALID_FORMAT;
}
-
-
static int
validate_latency(int latency)
{
@@ -111,18 +142,104 @@ validate_latency(int latency)
}
int
-cubeb_init(cubeb ** context, char const * context_name)
+cubeb_init(cubeb ** context, char const * context_name,
+ char const * backend_name)
{
- int (* init[])(cubeb **, char const *) = {
+ int (*init_oneshot)(cubeb **, char const *) = NULL;
+
+ if (backend_name != NULL) {
+ if (!strcmp(backend_name, "pulse")) {
+#if defined(USE_PULSE)
+ init_oneshot = pulse_init;
+#endif
+ } else if (!strcmp(backend_name, "pulse-rust")) {
+#if defined(USE_PULSE_RUST)
+ init_oneshot = pulse_rust_init;
+#endif
+ } else if (!strcmp(backend_name, "jack")) {
#if defined(USE_JACK)
- jack_init,
+ init_oneshot = jack_init;
+#endif
+ } else if (!strcmp(backend_name, "alsa")) {
+#if defined(USE_ALSA)
+ init_oneshot = alsa_init;
+#endif
+ } else if (!strcmp(backend_name, "audiounit")) {
+#if defined(USE_AUDIOUNIT)
+ init_oneshot = audiounit_init;
+#endif
+ } else if (!strcmp(backend_name, "audiounit-rust")) {
+#if defined(USE_AUDIOUNIT_RUST)
+ init_oneshot = audiounit_rust_init;
+#endif
+ } else if (!strcmp(backend_name, "wasapi")) {
+#if defined(USE_WASAPI)
+ init_oneshot = wasapi_init;
+#endif
+ } else if (!strcmp(backend_name, "winmm")) {
+#if defined(USE_WINMM)
+ init_oneshot = winmm_init;
+#endif
+ } else if (!strcmp(backend_name, "sndio")) {
+#if defined(USE_SNDIO)
+ init_oneshot = sndio_init;
+#endif
+ } else if (!strcmp(backend_name, "sun")) {
+#if defined(USE_SUN)
+ init_oneshot = sun_init;
+#endif
+ } else if (!strcmp(backend_name, "opensl")) {
+#if defined(USE_OPENSL)
+ init_oneshot = opensl_init;
+#endif
+ } else if (!strcmp(backend_name, "oss")) {
+#if defined(USE_OSS)
+ init_oneshot = oss_init;
+#endif
+ } else if (!strcmp(backend_name, "aaudio")) {
+#if defined(USE_AAUDIO)
+ init_oneshot = aaudio_init;
+#endif
+ } else if (!strcmp(backend_name, "audiotrack")) {
+#if defined(USE_AUDIOTRACK)
+ init_oneshot = audiotrack_init;
+#endif
+ } else if (!strcmp(backend_name, "kai")) {
+#if defined(USE_KAI)
+ init_oneshot = kai_init;
+#endif
+ } else {
+ /* Already set */
+ }
+ }
+
+ int (*default_init[])(cubeb **, char const *) = {
+ /*
+ * init_oneshot must be at the top to allow user
+ * to override all other choices
+ */
+ init_oneshot,
+#if defined(USE_PULSE_RUST)
+ pulse_rust_init,
#endif
#if defined(USE_PULSE)
pulse_init,
#endif
+#if defined(USE_JACK)
+ jack_init,
+#endif
+#if defined(USE_SNDIO)
+ sndio_init,
+#endif
#if defined(USE_ALSA)
alsa_init,
#endif
+#if defined(USE_OSS)
+ oss_init,
+#endif
+#if defined(USE_AUDIOUNIT_RUST)
+ audiounit_rust_init,
+#endif
#if defined(USE_AUDIOUNIT)
audiounit_init,
#endif
@@ -132,21 +249,23 @@ cubeb_init(cubeb ** context, char const * context_name)
#if defined(USE_WINMM)
winmm_init,
#endif
-#if defined(USE_SNDIO)
- sndio_init,
+#if defined(USE_SUN)
+ sun_init,
#endif
#if defined(USE_OPENSL)
opensl_init,
#endif
+ // TODO: should probably be preferred over OpenSLES when available.
+ // Initialization will fail on old android devices.
+#if defined(USE_AAUDIO)
+ aaudio_init,
+#endif
#if defined(USE_AUDIOTRACK)
audiotrack_init,
#endif
#if defined(USE_KAI)
kai_init,
#endif
-#if defined(USE_SUN)
- sunaudio_init,
-#endif
};
int i;
@@ -154,10 +273,10 @@ cubeb_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR_INVALID_PARAMETER;
}
- for (i = 0; i < NELEMS(init); ++i) {
- if (init[i](context, context_name) == CUBEB_OK) {
+#define OK(fn) assert((*context)->ops->fn)
+ for (i = 0; i < NELEMS(default_init); ++i) {
+ if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) {
/* Assert that the minimal API is implemented. */
-#define OK(fn) assert((* context)->ops->fn)
OK(get_backend_id);
OK(destroy);
OK(stream_init);
@@ -168,7 +287,6 @@ cubeb_init(cubeb ** context, char const * context_name)
return CUBEB_OK;
}
}
-
return CUBEB_ERROR;
}
@@ -197,9 +315,10 @@ cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels)
}
int
-cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms)
+cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
+ uint32_t * latency_ms)
{
- if (!context || !latency_ms) {
+ if (!context || !params || !latency_ms) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
@@ -207,7 +326,7 @@ cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * la
return CUBEB_ERROR_NOT_SUPPORTED;
}
- return context->ops->get_min_latency(context, params, latency_ms);
+ return context->ops->get_min_latency(context, *params, latency_ms);
}
int
@@ -235,36 +354,39 @@ cubeb_destroy(cubeb * context)
}
int
-cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
+cubeb_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
- unsigned int latency,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
+ unsigned int latency, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
{
int r;
- if (!context || !stream) {
+ if (!context || !stream || !data_callback || !state_callback) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
- if ((r = validate_stream_params(input_stream_params, output_stream_params)) != CUBEB_OK ||
+ if ((r = validate_stream_params(input_stream_params, output_stream_params)) !=
+ CUBEB_OK ||
(r = validate_latency(latency)) != CUBEB_OK) {
return r;
}
- return context->ops->stream_init(context, stream, stream_name,
- input_device,
- input_stream_params,
- output_device,
- output_stream_params,
- latency,
- data_callback,
- state_callback,
- user_ptr);
+ r = context->ops->stream_init(context, stream, stream_name, input_device,
+ input_stream_params, output_device,
+ output_stream_params, latency, data_callback,
+ state_callback, user_ptr);
+
+ if (r == CUBEB_ERROR_INVALID_FORMAT) {
+ LOG("Invalid format, %p %p %d %d", output_stream_params,
+ input_stream_params,
+ output_stream_params && output_stream_params->format,
+ input_stream_params && input_stream_params->format);
+ }
+
+ return r;
}
void
@@ -322,6 +444,20 @@ cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
}
int
+cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency)
+{
+ if (!stream || !latency) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_get_input_latency) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_get_input_latency(stream, latency);
+}
+
+int
cubeb_stream_set_volume(cubeb_stream * stream, float volume)
{
if (!stream || volume > 1.0 || volume < 0.0) {
@@ -335,21 +471,23 @@ cubeb_stream_set_volume(cubeb_stream * stream, float volume)
return stream->context->ops->stream_set_volume(stream, volume);
}
-int cubeb_stream_set_panning(cubeb_stream * stream, float panning)
+int
+cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name)
{
- if (!stream || panning < -1.0 || panning > 1.0) {
+ if (!stream || !stream_name) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
- if (!stream->context->ops->stream_set_panning) {
+ if (!stream->context->ops->stream_set_name) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
- return stream->context->ops->stream_set_panning(stream, panning);
+ return stream->context->ops->stream_set_name(stream, stream_name);
}
-int cubeb_stream_get_current_device(cubeb_stream * stream,
- cubeb_device ** const device)
+int
+cubeb_stream_get_current_device(cubeb_stream * stream,
+ cubeb_device ** const device)
{
if (!stream || !device) {
return CUBEB_ERROR_INVALID_PARAMETER;
@@ -362,8 +500,8 @@ int cubeb_stream_get_current_device(cubeb_stream * stream,
return stream->context->ops->stream_get_current_device(stream, device);
}
-int cubeb_stream_device_destroy(cubeb_stream * stream,
- cubeb_device * device)
+int
+cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
{
if (!stream || !device) {
return CUBEB_ERROR_INVALID_PARAMETER;
@@ -376,8 +514,10 @@ int cubeb_stream_device_destroy(cubeb_stream * stream,
return stream->context->ops->stream_device_destroy(stream, device);
}
-int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
- cubeb_device_changed_callback device_changed_callback)
+int
+cubeb_stream_register_device_changed_callback(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback)
{
if (!stream) {
return CUBEB_ERROR_INVALID_PARAMETER;
@@ -387,59 +527,70 @@ int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
return CUBEB_ERROR_NOT_SUPPORTED;
}
- return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback);
+ return stream->context->ops->stream_register_device_changed_callback(
+ stream, device_changed_callback);
}
-static
-void log_device(cubeb_device_info * device_info)
+void *
+cubeb_stream_user_ptr(cubeb_stream * stream)
+{
+ if (!stream) {
+ return NULL;
+ }
+
+ return stream->user_ptr;
+}
+
+static void
+log_device(cubeb_device_info * device_info)
{
char devfmts[128] = "";
- const char * devtype, * devstate, * devdeffmt;
+ const char *devtype, *devstate, *devdeffmt;
switch (device_info->type) {
- case CUBEB_DEVICE_TYPE_INPUT:
- devtype = "input";
- break;
- case CUBEB_DEVICE_TYPE_OUTPUT:
- devtype = "output";
- break;
- case CUBEB_DEVICE_TYPE_UNKNOWN:
- default:
- devtype = "unknown?";
- break;
+ case CUBEB_DEVICE_TYPE_INPUT:
+ devtype = "input";
+ break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ devtype = "output";
+ break;
+ case CUBEB_DEVICE_TYPE_UNKNOWN:
+ default:
+ devtype = "unknown?";
+ break;
};
switch (device_info->state) {
- case CUBEB_DEVICE_STATE_DISABLED:
- devstate = "disabled";
- break;
- case CUBEB_DEVICE_STATE_UNPLUGGED:
- devstate = "unplugged";
- break;
- case CUBEB_DEVICE_STATE_ENABLED:
- devstate = "enabled";
- break;
- default:
- devstate = "unknown?";
- break;
+ case CUBEB_DEVICE_STATE_DISABLED:
+ devstate = "disabled";
+ break;
+ case CUBEB_DEVICE_STATE_UNPLUGGED:
+ devstate = "unplugged";
+ break;
+ case CUBEB_DEVICE_STATE_ENABLED:
+ devstate = "enabled";
+ break;
+ default:
+ devstate = "unknown?";
+ break;
};
switch (device_info->default_format) {
- case CUBEB_DEVICE_FMT_S16LE:
- devdeffmt = "S16LE";
- break;
- case CUBEB_DEVICE_FMT_S16BE:
- devdeffmt = "S16BE";
- break;
- case CUBEB_DEVICE_FMT_F32LE:
- devdeffmt = "F32LE";
- break;
- case CUBEB_DEVICE_FMT_F32BE:
- devdeffmt = "F32BE";
- break;
- default:
- devdeffmt = "unknown?";
- break;
+ case CUBEB_DEVICE_FMT_S16LE:
+ devdeffmt = "S16LE";
+ break;
+ case CUBEB_DEVICE_FMT_S16BE:
+ devdeffmt = "S16BE";
+ break;
+ case CUBEB_DEVICE_FMT_F32LE:
+ devdeffmt = "F32LE";
+ break;
+ case CUBEB_DEVICE_FMT_F32BE:
+ devdeffmt = "F32BE";
+ break;
+ default:
+ devdeffmt = "unknown?";
+ break;
};
if (device_info->format & CUBEB_DEVICE_FMT_S16LE) {
@@ -466,20 +617,17 @@ void log_device(cubeb_device_info * device_info)
"\tRate:\t[%u, %u] (default: %u)\n"
"\tLatency: lo %u frames, hi %u frames",
device_info->device_id, device_info->preferred ? " (PREFERRED)" : "",
- device_info->friendly_name,
- device_info->group_id,
- device_info->vendor_name,
- devtype,
- devstate,
- device_info->max_channels,
- (devfmts[0] == '\0') ? devfmts : devfmts + 1, (unsigned int)device_info->format, devdeffmt,
- device_info->min_rate, device_info->max_rate, device_info->default_rate,
- device_info->latency_lo, device_info->latency_hi);
+ device_info->friendly_name, device_info->group_id,
+ device_info->vendor_name, devtype, devstate, device_info->max_channels,
+ (devfmts[0] == '\0') ? devfmts : devfmts + 1,
+ (unsigned int)device_info->format, devdeffmt, device_info->min_rate,
+ device_info->max_rate, device_info->default_rate, device_info->latency_lo,
+ device_info->latency_hi);
}
-int cubeb_enumerate_devices(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection ** collection)
+int
+cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection * collection)
{
int rv;
if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
@@ -491,61 +639,59 @@ int cubeb_enumerate_devices(cubeb * context,
rv = context->ops->enumerate_devices(context, devtype, collection);
- if (g_log_callback) {
- for (uint32_t i = 0; i < (*collection)->count; i++) {
- log_device((*collection)->device[i]);
+ if (g_cubeb_log_callback) {
+ for (size_t i = 0; i < collection->count; i++) {
+ log_device(&collection->device[i]);
}
}
return rv;
}
-int cubeb_device_collection_destroy(cubeb_device_collection * collection)
+int
+cubeb_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
{
- uint32_t i;
+ int r;
- if (collection == NULL)
+ if (context == NULL || collection == NULL)
return CUBEB_ERROR_INVALID_PARAMETER;
- for (i = 0; i < collection->count; i++)
- cubeb_device_info_destroy(collection->device[i]);
+ if (!context->ops->device_collection_destroy)
+ return CUBEB_ERROR_NOT_SUPPORTED;
- free(collection);
- return CUBEB_OK;
-}
+ if (!collection->device)
+ return CUBEB_OK;
-int cubeb_device_info_destroy(cubeb_device_info * info)
-{
- if (info == NULL) {
- return CUBEB_ERROR_INVALID_PARAMETER;
+ r = context->ops->device_collection_destroy(context, collection);
+ if (r == CUBEB_OK) {
+ collection->device = NULL;
+ collection->count = 0;
}
- free(info->device_id);
- free(info->friendly_name);
- free(info->group_id);
- free(info->vendor_name);
-
- free(info);
- return CUBEB_OK;
+ return r;
}
-int cubeb_register_device_collection_changed(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback callback,
- void * user_ptr)
+int
+cubeb_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback callback, void * user_ptr)
{
- if (context == NULL || (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
+ if (context == NULL ||
+ (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
return CUBEB_ERROR_INVALID_PARAMETER;
if (!context->ops->register_device_collection_changed) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
- return context->ops->register_device_collection_changed(context, devtype, callback, user_ptr);
+ return context->ops->register_device_collection_changed(context, devtype,
+ callback, user_ptr);
}
-int cubeb_set_log_callback(cubeb_log_level log_level,
- cubeb_log_callback log_callback)
+int
+cubeb_set_log_callback(cubeb_log_level log_level,
+ cubeb_log_callback log_callback)
{
if (log_level < CUBEB_LOG_DISABLED || log_level > CUBEB_LOG_VERBOSE) {
return CUBEB_ERROR_INVALID_FORMAT;
@@ -555,20 +701,21 @@ int cubeb_set_log_callback(cubeb_log_level log_level,
return CUBEB_ERROR_INVALID_PARAMETER;
}
- if (g_log_callback && log_callback) {
+ if (g_cubeb_log_callback && log_callback) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
- g_log_callback = log_callback;
- g_log_level = log_level;
+ g_cubeb_log_callback = log_callback;
+ g_cubeb_log_level = log_level;
- return CUBEB_OK;
-}
+ // Logging a message here allows to initialize the asynchronous logger from a
+ // thread that is not the audio rendering thread, and especially to not
+ // initialize it the first time we find a verbose log, which is often in the
+ // audio rendering callback, that runs from the audio rendering thread, and
+ // that is high priority, and that we don't want to block.
+ if (log_level >= CUBEB_LOG_VERBOSE) {
+ ALOGV("Starting cubeb log");
+ }
-void
-cubeb_crash()
-{
- *((volatile int *) NULL) = 0;
- abort();
+ return CUBEB_OK;
}
-
diff --git a/media/libcubeb/src/cubeb_aaudio.cpp b/media/libcubeb/src/cubeb_aaudio.cpp
new file mode 100644
index 0000000000..448ea918e0
--- /dev/null
+++ b/media/libcubeb/src/cubeb_aaudio.cpp
@@ -0,0 +1,1504 @@
+/* ex: set tabstop=2 shiftwidth=2 expandtab:
+ * Copyright © 2019 Jan Kelling
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_android.h"
+#include "cubeb_log.h"
+#include "cubeb_resampler.h"
+#include <aaudio/AAudio.h>
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <condition_variable>
+#include <cstring>
+#include <dlfcn.h>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <time.h>
+
+#ifdef DISABLE_LIBAAUDIO_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) cubeb_##x
+#define LIBAAUDIO_API_VISIT(X) \
+ X(AAudio_convertResultToText) \
+ X(AAudio_convertStreamStateToText) \
+ X(AAudio_createStreamBuilder) \
+ X(AAudioStreamBuilder_openStream) \
+ X(AAudioStreamBuilder_setChannelCount) \
+ X(AAudioStreamBuilder_setBufferCapacityInFrames) \
+ X(AAudioStreamBuilder_setDirection) \
+ X(AAudioStreamBuilder_setFormat) \
+ X(AAudioStreamBuilder_setSharingMode) \
+ X(AAudioStreamBuilder_setPerformanceMode) \
+ X(AAudioStreamBuilder_setSampleRate) \
+ X(AAudioStreamBuilder_delete) \
+ X(AAudioStreamBuilder_setDataCallback) \
+ X(AAudioStreamBuilder_setErrorCallback) \
+ X(AAudioStream_close) \
+ X(AAudioStream_read) \
+ X(AAudioStream_requestStart) \
+ X(AAudioStream_requestPause) \
+ X(AAudioStream_setBufferSizeInFrames) \
+ X(AAudioStream_getTimestamp) \
+ X(AAudioStream_requestFlush) \
+ X(AAudioStream_requestStop) \
+ X(AAudioStream_getPerformanceMode) \
+ X(AAudioStream_getSharingMode) \
+ X(AAudioStream_getBufferSizeInFrames) \
+ X(AAudioStream_getBufferCapacityInFrames) \
+ X(AAudioStream_getSampleRate) \
+ X(AAudioStream_waitForStateChange) \
+ X(AAudioStream_getFramesRead) \
+ X(AAudioStream_getState) \
+ X(AAudioStream_getFramesWritten) \
+ X(AAudioStream_getFramesPerBurst) \
+ X(AAudioStreamBuilder_setInputPreset) \
+ X(AAudioStreamBuilder_setUsage)
+
+// not needed or added later on
+// X(AAudioStreamBuilder_setFramesPerDataCallback) \
+ // X(AAudioStreamBuilder_setDeviceId) \
+ // X(AAudioStreamBuilder_setSamplesPerFrame) \
+ // X(AAudioStream_getSamplesPerFrame) \
+ // X(AAudioStream_getDeviceId) \
+ // X(AAudioStream_write) \
+ // X(AAudioStream_getChannelCount) \
+ // X(AAudioStream_getFormat) \
+ // X(AAudioStream_getXRunCount) \
+ // X(AAudioStream_isMMapUsed) \
+ // X(AAudioStreamBuilder_setContentType) \
+ // X(AAudioStreamBuilder_setSessionId) \
+ // X(AAudioStream_getUsage) \
+ // X(AAudioStream_getContentType) \
+ // X(AAudioStream_getInputPreset) \
+ // X(AAudioStream_getSessionId) \
+
+#define MAKE_TYPEDEF(x) static decltype(x) * cubeb_##x;
+LIBAAUDIO_API_VISIT(MAKE_TYPEDEF)
+#undef MAKE_TYPEDEF
+#endif
+
+const uint8_t MAX_STREAMS = 16;
+
+using unique_lock = std::unique_lock<std::mutex>;
+using lock_guard = std::lock_guard<std::mutex>;
+
+enum class stream_state {
+ INIT = 0,
+ STOPPED,
+ STOPPING,
+ STARTED,
+ STARTING,
+ DRAINING,
+ ERROR,
+ SHUTDOWN,
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context{};
+ void * user_ptr{};
+
+ std::atomic<bool> in_use{false};
+ std::atomic<stream_state> state{stream_state::INIT};
+
+ AAudioStream * ostream{};
+ AAudioStream * istream{};
+ cubeb_data_callback data_callback{};
+ cubeb_state_callback state_callback{};
+ cubeb_resampler * resampler{};
+
+ // mutex synchronizes access to the stream from the state thread
+ // and user-called functions. Everything that is accessed in the
+ // aaudio data (or error) callback is synchronized only via atomics.
+ std::mutex mutex;
+
+ std::unique_ptr<char[]> in_buf;
+ unsigned in_frame_size{}; // size of one input frame
+
+ cubeb_sample_format out_format{};
+ std::atomic<float> volume{1.f};
+ unsigned out_channels{};
+ unsigned out_frame_size{};
+ int64_t latest_output_latency = 0;
+ int64_t latest_input_latency = 0;
+ bool voice_input;
+ bool voice_output;
+ uint64_t previous_clock;
+};
+
+struct cubeb {
+ struct cubeb_ops const * ops{};
+ void * libaaudio{};
+
+ struct {
+ // The state thread: it waits for state changes and stops
+ // drained streams.
+ std::thread thread;
+ std::thread notifier;
+ std::mutex mutex;
+ std::condition_variable cond;
+ std::atomic<bool> join{false};
+ std::atomic<bool> waiting{false};
+ } state;
+
+ // streams[i].in_use signals whether a stream is used
+ struct cubeb_stream streams[MAX_STREAMS];
+};
+
+// Only allowed from state thread, while mutex on stm is locked
+static void
+shutdown(cubeb_stream * stm)
+{
+ if (stm->istream) {
+ WRAP(AAudioStream_requestStop)(stm->istream);
+ }
+ if (stm->ostream) {
+ WRAP(AAudioStream_requestStop)(stm->ostream);
+ }
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ stm->state.store(stream_state::SHUTDOWN);
+}
+
+// Returns whether the given state is one in which we wait for
+// an asynchronous change
+static bool
+waiting_state(stream_state state)
+{
+ switch (state) {
+ case stream_state::DRAINING:
+ case stream_state::STARTING:
+ case stream_state::STOPPING:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void
+update_state(cubeb_stream * stm)
+{
+ // Fast path for streams that don't wait for state change or are invalid
+ enum stream_state old_state = stm->state.load();
+ if (old_state == stream_state::INIT || old_state == stream_state::STARTED ||
+ old_state == stream_state::STOPPED ||
+ old_state == stream_state::SHUTDOWN) {
+ return;
+ }
+
+ // If the main thread currently operates on this thread, we don't
+ // have to wait for it
+ unique_lock lock(stm->mutex, std::try_to_lock);
+ if (!lock.owns_lock()) {
+ return;
+ }
+
+ // check again: if this is true now, the stream was destroyed or
+ // changed between our fast path check and locking the mutex
+ old_state = stm->state.load();
+ if (old_state == stream_state::INIT || old_state == stream_state::STARTED ||
+ old_state == stream_state::STOPPED ||
+ old_state == stream_state::SHUTDOWN) {
+ return;
+ }
+
+ // We compute the new state the stream has and then compare_exchange it
+ // if it has changed. This way we will never just overwrite state
+ // changes that were set from the audio thread in the meantime,
+ // such as a DRAINING or error state.
+ enum stream_state new_state;
+ do {
+ if (old_state == stream_state::SHUTDOWN) {
+ return;
+ }
+
+ if (old_state == stream_state::ERROR) {
+ shutdown(stm);
+ return;
+ }
+
+ new_state = old_state;
+
+ aaudio_stream_state_t istate = 0;
+ aaudio_stream_state_t ostate = 0;
+
+ // We use waitForStateChange (with zero timeout) instead of just
+ // getState since only the former internally updates the state.
+ // See the docs of aaudio getState/waitForStateChange for details,
+ // why we are passing STATE_UNKNOWN.
+ aaudio_result_t res;
+ if (stm->istream) {
+ res = WRAP(AAudioStream_waitForStateChange)(
+ stm->istream, AAUDIO_STREAM_STATE_UNKNOWN, &istate, 0);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_waitForStateChanged: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ assert(istate);
+ }
+
+ if (stm->ostream) {
+ res = WRAP(AAudioStream_waitForStateChange)(
+ stm->ostream, AAUDIO_STREAM_STATE_UNKNOWN, &ostate, 0);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_waitForStateChanged: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ assert(ostate);
+ }
+
+ // handle invalid stream states
+ if (istate == AAUDIO_STREAM_STATE_PAUSING ||
+ istate == AAUDIO_STREAM_STATE_PAUSED ||
+ istate == AAUDIO_STREAM_STATE_FLUSHING ||
+ istate == AAUDIO_STREAM_STATE_FLUSHED ||
+ istate == AAUDIO_STREAM_STATE_UNKNOWN ||
+ istate == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ const char * name = WRAP(AAudio_convertStreamStateToText)(istate);
+ LOG("Unexpected android input stream state %s", name);
+ shutdown(stm);
+ return;
+ }
+
+ if (ostate == AAUDIO_STREAM_STATE_PAUSING ||
+ ostate == AAUDIO_STREAM_STATE_PAUSED ||
+ ostate == AAUDIO_STREAM_STATE_FLUSHING ||
+ ostate == AAUDIO_STREAM_STATE_FLUSHED ||
+ ostate == AAUDIO_STREAM_STATE_UNKNOWN ||
+ ostate == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ const char * name = WRAP(AAudio_convertStreamStateToText)(istate);
+ LOG("Unexpected android output stream state %s", name);
+ shutdown(stm);
+ return;
+ }
+
+ switch (old_state) {
+ case stream_state::STARTING:
+ if ((!istate || istate == AAUDIO_STREAM_STATE_STARTED) &&
+ (!ostate || ostate == AAUDIO_STREAM_STATE_STARTED)) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+ new_state = stream_state::STARTED;
+ }
+ break;
+ case stream_state::DRAINING:
+ // The DRAINING state means that we want to stop the streams but
+ // may not have done so yet.
+ // The aaudio docs state that returning STOP from the callback isn't
+ // enough, the stream has to be stopped from another thread
+ // afterwards.
+ // No callbacks are triggered anymore when requestStop returns.
+ // That is important as we otherwise might read from a closed istream
+ // for a duplex stream.
+ // Therefor it is important to close ostream first.
+ if (ostate && ostate != AAUDIO_STREAM_STATE_STOPPING &&
+ ostate != AAUDIO_STREAM_STATE_STOPPED) {
+ res = WRAP(AAudioStream_requestStop)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ }
+ if (istate && istate != AAUDIO_STREAM_STATE_STOPPING &&
+ istate != AAUDIO_STREAM_STATE_STOPPED) {
+ res = WRAP(AAudioStream_requestStop)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ }
+
+ // we always wait until both streams are stopped until we
+ // send CUBEB_STATE_DRAINED. Then we can directly transition
+ // our logical state to STOPPED, not triggering
+ // an additional CUBEB_STATE_STOPPED callback (which might
+ // be unexpected for the user).
+ if ((!ostate || ostate == AAUDIO_STREAM_STATE_STOPPED) &&
+ (!istate || istate == AAUDIO_STREAM_STATE_STOPPED)) {
+ new_state = stream_state::STOPPED;
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ }
+ break;
+ case stream_state::STOPPING:
+ assert(!istate || istate == AAUDIO_STREAM_STATE_STOPPING ||
+ istate == AAUDIO_STREAM_STATE_STOPPED);
+ assert(!ostate || ostate == AAUDIO_STREAM_STATE_STOPPING ||
+ ostate == AAUDIO_STREAM_STATE_STOPPED);
+ if ((!istate || istate == AAUDIO_STREAM_STATE_STOPPED) &&
+ (!ostate || ostate == AAUDIO_STREAM_STATE_STOPPED)) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+ new_state = stream_state::STOPPED;
+ }
+ break;
+ default:
+ assert(false && "Unreachable: invalid state");
+ }
+ } while (old_state != new_state &&
+ !stm->state.compare_exchange_strong(old_state, new_state));
+}
+
+// See https://nyorain.github.io/lock-free-wakeup.html for a note
+// why this is needed. The audio thread notifies the state thread about
+// state changes and must not block. The state thread on the other hand should
+// sleep until there is work to be done. So we need a lockfree producer
+// and blocking producer. This can only be achieved safely with a new thread
+// that only serves as notifier backup (in case the notification happens
+// right between the state thread checking and going to sleep in which case
+// this thread will kick in and signal it right again).
+static void
+notifier_thread(cubeb * ctx)
+{
+ unique_lock lock(ctx->state.mutex);
+
+ while (!ctx->state.join.load()) {
+ ctx->state.cond.wait(lock);
+ if (ctx->state.waiting.load()) {
+ // This must signal our state thread since there is no other
+ // thread currently waiting on the condition variable.
+ // The state change thread is guaranteed to be waiting since
+ // we hold the mutex it locks when awake.
+ ctx->state.cond.notify_one();
+ }
+ }
+
+ // make sure other thread joins as well
+ ctx->state.cond.notify_one();
+ LOG("Exiting notifier thread");
+}
+
+static void
+state_thread(cubeb * ctx)
+{
+ unique_lock lock(ctx->state.mutex);
+
+ bool waiting = false;
+ while (!ctx->state.join.load()) {
+ waiting |= ctx->state.waiting.load();
+ if (waiting) {
+ ctx->state.waiting.store(false);
+ waiting = false;
+ for (unsigned i = 0u; i < MAX_STREAMS; ++i) {
+ cubeb_stream * stm = &ctx->streams[i];
+ update_state(stm);
+ waiting |= waiting_state(atomic_load(&stm->state));
+ }
+
+ // state changed from another thread, update again immediately
+ if (ctx->state.waiting.load()) {
+ waiting = true;
+ continue;
+ }
+
+ // Not waiting for any change anymore: we can wait on the
+ // condition variable without timeout
+ if (!waiting) {
+ continue;
+ }
+
+ // while any stream is waiting for state change we sleep with regular
+ // timeouts. But we wake up immediately if signaled.
+ // This might seem like a poor man's implementation of state change
+ // waiting but (as of october 2020), the implementation of
+ // AAudioStream_waitForStateChange is just sleeping with regular
+ // timeouts as well:
+ // https://android.googlesource.com/platform/frameworks/av/+/refs/heads/master/media/libaaudio/src/core/AudioStream.cpp
+ auto dur = std::chrono::milliseconds(5);
+ ctx->state.cond.wait_for(lock, dur);
+ } else {
+ ctx->state.cond.wait(lock);
+ }
+ }
+
+ // make sure other thread joins as well
+ ctx->state.cond.notify_one();
+ LOG("Exiting state thread");
+}
+
+static char const *
+aaudio_get_backend_id(cubeb * /* ctx */)
+{
+ return "aaudio";
+}
+
+static int
+aaudio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ assert(ctx && max_channels);
+ // NOTE: we might get more, AAudio docs don't specify anything.
+ *max_channels = 2;
+ return CUBEB_OK;
+}
+
+static void
+aaudio_destroy(cubeb * ctx)
+{
+ assert(ctx);
+
+#ifndef NDEBUG
+ // make sure all streams were destroyed
+ for (unsigned i = 0u; i < MAX_STREAMS; ++i) {
+ assert(!ctx->streams[i].in_use.load());
+ }
+#endif
+
+ // broadcast joining to both threads
+ // they will additionally signal each other before joining
+ ctx->state.join.store(true);
+ ctx->state.cond.notify_all();
+
+ if (ctx->state.thread.joinable()) {
+ ctx->state.thread.join();
+ }
+ if (ctx->state.notifier.joinable()) {
+ ctx->state.notifier.join();
+ }
+
+ if (ctx->libaaudio) {
+ dlclose(ctx->libaaudio);
+ }
+ delete ctx;
+}
+
+static void
+apply_volume(cubeb_stream * stm, void * audio_data, uint32_t num_frames)
+{
+ float volume = stm->volume.load();
+ // optimization: we don't have to change anything in this case
+ if (volume == 1.f) {
+ return;
+ }
+
+ switch (stm->out_format) {
+ case CUBEB_SAMPLE_S16NE:
+ for (uint32_t i = 0u; i < num_frames * stm->out_channels; ++i) {
+ (static_cast<int16_t *>(audio_data))[i] *= volume;
+ }
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ for (uint32_t i = 0u; i < num_frames * stm->out_channels; ++i) {
+ (static_cast<float *>(audio_data))[i] *= volume;
+ }
+ break;
+ default:
+ assert(false && "Unreachable: invalid stream out_format");
+ }
+}
+
+// Returning AAUDIO_CALLBACK_RESULT_STOP seems to put the stream in
+// an invalid state. Seems like an AAudio bug/bad documentation.
+// We therefore only return it on error.
+
+static aaudio_data_callback_result_t
+aaudio_duplex_data_cb(AAudioStream * astream, void * user_data,
+ void * audio_data, int32_t num_frames)
+{
+ cubeb_stream * stm = (cubeb_stream *)user_data;
+ assert(stm->ostream == astream);
+ assert(stm->istream);
+ assert(num_frames >= 0);
+
+ stream_state state = atomic_load(&stm->state);
+ // int istate = WRAP(AAudioStream_getState)(stm->istream);
+ // int ostate = WRAP(AAudioStream_getState)(stm->ostream);
+ // ALOGV("aaudio duplex data cb on stream %p: state %ld (in: %d, out: %d),
+ // num_frames: %ld",
+ // (void*) stm, state, istate, ostate, num_frames);
+
+ // all other states may happen since the callback might be called
+ // from within requestStart
+ assert(state != stream_state::SHUTDOWN);
+
+ // This might happen when we started draining but not yet actually
+ // stopped the stream from the state thread.
+ if (state == stream_state::DRAINING) {
+ std::memset(audio_data, 0x0, num_frames * stm->out_frame_size);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ // The aaudio docs state that AAudioStream_read must not be called on
+ // the stream associated with a callback. But we call it on the input stream
+ // while this callback is for the output stream so this is ok.
+ // We also pass timeout 0, giving us strong non-blocking guarantees.
+ // This is exactly how it's done in the aaudio duplex example code snippet.
+ long in_num_frames =
+ WRAP(AAudioStream_read)(stm->istream, stm->in_buf.get(), num_frames, 0);
+ if (in_num_frames < 0) { // error
+ stm->state.store(stream_state::ERROR);
+ LOG("AAudioStream_read: %s",
+ WRAP(AAudio_convertResultToText)(in_num_frames));
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ }
+
+ // This can happen shortly after starting the stream. AAudio might immediately
+ // begin to buffer output but not have any input ready yet. We could
+ // block AAudioStream_read (passing a timeout > 0) but that leads to issues
+ // since blocking in this callback is a bad idea in general and it might break
+ // the stream when it is stopped by another thread shortly after being
+ // started. We therefore simply send silent input to the application, as shown
+ // in the AAudio duplex stream code example.
+ if (in_num_frames < num_frames) {
+ // LOG("AAudioStream_read returned not enough frames: %ld instead of %d",
+ // in_num_frames, num_frames);
+ unsigned left = num_frames - in_num_frames;
+ char * buf = stm->in_buf.get() + in_num_frames * stm->in_frame_size;
+ std::memset(buf, 0x0, left * stm->in_frame_size);
+ in_num_frames = num_frames;
+ }
+
+ long done_frames =
+ cubeb_resampler_fill(stm->resampler, stm->in_buf.get(), &in_num_frames,
+ audio_data, num_frames);
+
+ if (done_frames < 0 || done_frames > num_frames) {
+ LOG("Error in data callback or resampler: %ld", done_frames);
+ stm->state.store(stream_state::ERROR);
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ } else if (done_frames < num_frames) {
+ stm->state.store(stream_state::DRAINING);
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+
+ char * begin =
+ static_cast<char *>(audio_data) + done_frames * stm->out_frame_size;
+ std::memset(begin, 0x0, (num_frames - done_frames) * stm->out_frame_size);
+ }
+
+ apply_volume(stm, audio_data, done_frames);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static aaudio_data_callback_result_t
+aaudio_output_data_cb(AAudioStream * astream, void * user_data,
+ void * audio_data, int32_t num_frames)
+{
+ cubeb_stream * stm = (cubeb_stream *)user_data;
+ assert(stm->ostream == astream);
+ assert(!stm->istream);
+ assert(num_frames >= 0);
+
+ stream_state state = stm->state.load();
+ // int ostate = WRAP(AAudioStream_getState)(stm->ostream);
+ // ALOGV("aaudio output data cb on stream %p: state %ld (%d), num_frames:
+ // %ld",
+ // (void*) stm, state, ostate, num_frames);
+
+ // all other states may happen since the callback might be called
+ // from within requestStart
+ assert(state != stream_state::SHUTDOWN);
+
+ // This might happen when we started draining but not yet actually
+ // stopped the stream from the state thread.
+ if (state == stream_state::DRAINING) {
+ std::memset(audio_data, 0x0, num_frames * stm->out_frame_size);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ long done_frames =
+ cubeb_resampler_fill(stm->resampler, NULL, NULL, audio_data, num_frames);
+ if (done_frames < 0 || done_frames > num_frames) {
+ LOG("Error in data callback or resampler: %ld", done_frames);
+ stm->state.store(stream_state::ERROR);
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ } else if (done_frames < num_frames) {
+ stm->state.store(stream_state::DRAINING);
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+
+ char * begin =
+ static_cast<char *>(audio_data) + done_frames * stm->out_frame_size;
+ std::memset(begin, 0x0, (num_frames - done_frames) * stm->out_frame_size);
+ }
+
+ apply_volume(stm, audio_data, done_frames);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static aaudio_data_callback_result_t
+aaudio_input_data_cb(AAudioStream * astream, void * user_data,
+ void * audio_data, int32_t num_frames)
+{
+ cubeb_stream * stm = (cubeb_stream *)user_data;
+ assert(stm->istream == astream);
+ assert(!stm->ostream);
+ assert(num_frames >= 0);
+
+ stream_state state = stm->state.load();
+ // int istate = WRAP(AAudioStream_getState)(stm->istream);
+ // ALOGV("aaudio input data cb on stream %p: state %ld (%d), num_frames: %ld",
+ // (void*) stm, state, istate, num_frames);
+
+ // all other states may happen since the callback might be called
+ // from within requestStart
+ assert(state != stream_state::SHUTDOWN);
+
+ // This might happen when we started draining but not yet actually
+ // STOPPED the stream from the state thread.
+ if (state == stream_state::DRAINING) {
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ long input_frame_count = num_frames;
+ long done_frames = cubeb_resampler_fill(stm->resampler, audio_data,
+ &input_frame_count, NULL, 0);
+ if (done_frames < 0 || done_frames > num_frames) {
+ LOG("Error in data callback or resampler: %ld", done_frames);
+ stm->state.store(stream_state::ERROR);
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ } else if (done_frames < input_frame_count) {
+ // we don't really drain an input stream, just have to
+ // stop it from the state thread. That is signaled via the
+ // DRAINING state.
+ stm->state.store(stream_state::DRAINING);
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+ }
+
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static void
+aaudio_error_cb(AAudioStream * astream, void * user_data, aaudio_result_t error)
+{
+ cubeb_stream * stm = static_cast<cubeb_stream *>(user_data);
+ assert(stm->ostream == astream || stm->istream == astream);
+ LOG("AAudio error callback: %s", WRAP(AAudio_convertResultToText)(error));
+ stm->state.store(stream_state::ERROR);
+}
+
+static int
+realize_stream(AAudioStreamBuilder * sb, const cubeb_stream_params * params,
+ AAudioStream ** stream, unsigned * frame_size)
+{
+ aaudio_result_t res;
+ assert(params->rate);
+ assert(params->channels);
+
+ WRAP(AAudioStreamBuilder_setSampleRate)(sb, params->rate);
+ WRAP(AAudioStreamBuilder_setChannelCount)(sb, params->channels);
+
+ aaudio_format_t fmt;
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16NE:
+ fmt = AAUDIO_FORMAT_PCM_I16;
+ *frame_size = sizeof(int16_t) * params->channels;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ fmt = AAUDIO_FORMAT_PCM_FLOAT;
+ *frame_size = sizeof(float) * params->channels;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ WRAP(AAudioStreamBuilder_setFormat)(sb, fmt);
+ res = WRAP(AAudioStreamBuilder_openStream)(sb, stream);
+ if (res == AAUDIO_ERROR_INVALID_FORMAT) {
+ LOG("AAudio device doesn't support output format %d", fmt);
+ return CUBEB_ERROR_INVALID_FORMAT;
+ } else if (params->rate && res == AAUDIO_ERROR_INVALID_RATE) {
+ // The requested rate is not supported.
+ // Just try again with default rate, we create a resampler anyways
+ WRAP(AAudioStreamBuilder_setSampleRate)(sb, AAUDIO_UNSPECIFIED);
+ res = WRAP(AAudioStreamBuilder_openStream)(sb, stream);
+ LOG("Requested rate of %u is not supported, inserting resampler",
+ params->rate);
+ }
+
+ // When the app has no permission to record audio
+ // (android.permission.RECORD_AUDIO) but requested and input stream, this will
+ // return INVALID_ARGUMENT.
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStreamBuilder_openStream: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static void
+aaudio_stream_destroy(cubeb_stream * stm)
+{
+ lock_guard lock(stm->mutex);
+ assert(stm->state == stream_state::STOPPED ||
+ stm->state == stream_state::STOPPING ||
+ stm->state == stream_state::INIT ||
+ stm->state == stream_state::DRAINING ||
+ stm->state == stream_state::ERROR ||
+ stm->state == stream_state::SHUTDOWN);
+
+ aaudio_result_t res;
+
+ // No callbacks are triggered anymore when requestStop returns.
+ // That is important as we otherwise might read from a closed istream
+ // for a duplex stream.
+ if (stm->ostream) {
+ if (stm->state != stream_state::STOPPED &&
+ stm->state != stream_state::STOPPING &&
+ stm->state != stream_state::SHUTDOWN) {
+ res = WRAP(AAudioStream_requestStop)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStreamBuilder_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ }
+ }
+
+ WRAP(AAudioStream_close)(stm->ostream);
+ stm->ostream = NULL;
+ }
+
+ if (stm->istream) {
+ if (stm->state != stream_state::STOPPED &&
+ stm->state != stream_state::STOPPING &&
+ stm->state != stream_state::SHUTDOWN) {
+ res = WRAP(AAudioStream_requestStop)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStreamBuilder_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ }
+ }
+
+ WRAP(AAudioStream_close)(stm->istream);
+ stm->istream = NULL;
+ }
+
+ if (stm->resampler) {
+ cubeb_resampler_destroy(stm->resampler);
+ stm->resampler = NULL;
+ }
+
+ stm->in_buf = {};
+ stm->in_frame_size = {};
+ stm->out_format = {};
+ stm->out_channels = {};
+ stm->out_frame_size = {};
+
+ stm->state.store(stream_state::INIT);
+ stm->in_use.store(false);
+}
+
+static int
+aaudio_stream_init_impl(cubeb_stream * stm, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames)
+{
+ assert(stm->state.load() == stream_state::INIT);
+ stm->in_use.store(true);
+
+ aaudio_result_t res;
+ AAudioStreamBuilder * sb;
+ res = WRAP(AAudio_createStreamBuilder)(&sb);
+ if (res != AAUDIO_OK) {
+ LOG("AAudio_createStreamBuilder: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return CUBEB_ERROR;
+ }
+
+ // make sure the builder is always destroyed
+ struct StreamBuilderDestructor {
+ void operator()(AAudioStreamBuilder * sb)
+ {
+ WRAP(AAudioStreamBuilder_delete)(sb);
+ }
+ };
+
+ std::unique_ptr<AAudioStreamBuilder, StreamBuilderDestructor> sbPtr(sb);
+
+ WRAP(AAudioStreamBuilder_setErrorCallback)(sb, aaudio_error_cb, stm);
+ WRAP(AAudioStreamBuilder_setBufferCapacityInFrames)(sb, latency_frames);
+
+ AAudioStream_dataCallback in_data_callback{};
+ AAudioStream_dataCallback out_data_callback{};
+ if (output_stream_params && input_stream_params) {
+ out_data_callback = aaudio_duplex_data_cb;
+ in_data_callback = NULL;
+ } else if (input_stream_params) {
+ in_data_callback = aaudio_input_data_cb;
+ } else if (output_stream_params) {
+ out_data_callback = aaudio_output_data_cb;
+ } else {
+ LOG("Tried to open stream without input or output parameters");
+ return CUBEB_ERROR;
+ }
+
+#ifdef CUBEB_AAUDIO_EXCLUSIVE_STREAM
+ LOG("AAudio setting exclusive share mode for stream");
+ WRAP(AAudioStreamBuilder_setSharingMode)(sb, AAUDIO_SHARING_MODE_EXCLUSIVE);
+#endif
+
+ if (latency_frames <= POWERSAVE_LATENCY_FRAMES_THRESHOLD) {
+ LOG("AAudio setting low latency mode for stream");
+ WRAP(AAudioStreamBuilder_setPerformanceMode)
+ (sb, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+ } else {
+ LOG("AAudio setting power saving mode for stream");
+ WRAP(AAudioStreamBuilder_setPerformanceMode)
+ (sb, AAUDIO_PERFORMANCE_MODE_POWER_SAVING);
+ }
+
+ unsigned frame_size;
+
+ // initialize streams
+ // output
+ uint32_t target_sample_rate = 0;
+ cubeb_stream_params out_params;
+ if (output_stream_params) {
+ int output_preset = stm->voice_output ? AAUDIO_USAGE_VOICE_COMMUNICATION
+ : AAUDIO_USAGE_MEDIA;
+ WRAP(AAudioStreamBuilder_setUsage)(sb, output_preset);
+ WRAP(AAudioStreamBuilder_setDirection)(sb, AAUDIO_DIRECTION_OUTPUT);
+ WRAP(AAudioStreamBuilder_setDataCallback)(sb, out_data_callback, stm);
+ int res_err =
+ realize_stream(sb, output_stream_params, &stm->ostream, &frame_size);
+ if (res_err) {
+ return res_err;
+ }
+
+ // output debug information
+ aaudio_sharing_mode_t sm = WRAP(AAudioStream_getSharingMode)(stm->ostream);
+ aaudio_performance_mode_t pm =
+ WRAP(AAudioStream_getPerformanceMode)(stm->ostream);
+ int bcap = WRAP(AAudioStream_getBufferCapacityInFrames)(stm->ostream);
+ int bsize = WRAP(AAudioStream_getBufferSizeInFrames)(stm->ostream);
+ int rate = WRAP(AAudioStream_getSampleRate)(stm->ostream);
+ LOG("AAudio output stream sharing mode: %d", sm);
+ LOG("AAudio output stream performance mode: %d", pm);
+ LOG("AAudio output stream buffer capacity: %d", bcap);
+ LOG("AAudio output stream buffer size: %d", bsize);
+ LOG("AAudio output stream buffer rate: %d", rate);
+
+ target_sample_rate = output_stream_params->rate;
+ out_params = *output_stream_params;
+ out_params.rate = rate;
+
+ stm->out_channels = output_stream_params->channels;
+ stm->out_format = output_stream_params->format;
+ stm->out_frame_size = frame_size;
+ stm->volume.store(1.f);
+ }
+
+ // input
+ cubeb_stream_params in_params;
+ if (input_stream_params) {
+ // Match what the OpenSL backend does for now, we could use UNPROCESSED and
+ // VOICE_COMMUNICATION here, but we'd need to make it clear that
+ // application-level AEC and other voice processing should be disabled
+ // there.
+ int input_preset = stm->voice_input ? AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
+ : AAUDIO_INPUT_PRESET_CAMCORDER;
+ WRAP(AAudioStreamBuilder_setInputPreset)(sb, input_preset);
+ WRAP(AAudioStreamBuilder_setDirection)(sb, AAUDIO_DIRECTION_INPUT);
+ WRAP(AAudioStreamBuilder_setDataCallback)(sb, in_data_callback, stm);
+ int res_err =
+ realize_stream(sb, input_stream_params, &stm->istream, &frame_size);
+ if (res_err) {
+ return res_err;
+ }
+
+ // output debug information
+ aaudio_sharing_mode_t sm = WRAP(AAudioStream_getSharingMode)(stm->istream);
+ aaudio_performance_mode_t pm =
+ WRAP(AAudioStream_getPerformanceMode)(stm->istream);
+ int bcap = WRAP(AAudioStream_getBufferCapacityInFrames)(stm->istream);
+ int bsize = WRAP(AAudioStream_getBufferSizeInFrames)(stm->istream);
+ int rate = WRAP(AAudioStream_getSampleRate)(stm->istream);
+ LOG("AAudio input stream sharing mode: %d", sm);
+ LOG("AAudio input stream performance mode: %d", pm);
+ LOG("AAudio input stream buffer capacity: %d", bcap);
+ LOG("AAudio input stream buffer size: %d", bsize);
+ LOG("AAudio input stream buffer rate: %d", rate);
+
+ stm->in_buf.reset(new char[bcap * frame_size]());
+ assert(!target_sample_rate ||
+ target_sample_rate == input_stream_params->rate);
+
+ target_sample_rate = input_stream_params->rate;
+ in_params = *input_stream_params;
+ in_params.rate = rate;
+ stm->in_frame_size = frame_size;
+ }
+
+ // initialize resampler
+ stm->resampler = cubeb_resampler_create(
+ stm, input_stream_params ? &in_params : NULL,
+ output_stream_params ? &out_params : NULL, target_sample_rate,
+ stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT);
+
+ if (!stm->resampler) {
+ LOG("Failed to create resampler");
+ return CUBEB_ERROR;
+ }
+
+ // the stream isn't started initially. We don't need to differentiate
+ // between a stream that was just initialized and one that played
+ // already but was stopped.
+ stm->state.store(stream_state::STOPPED);
+ LOG("Cubeb stream (%p) INIT success", (void *)stm);
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_init(cubeb * ctx, cubeb_stream ** stream,
+ char const * /* stream_name */, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ assert(!input_device);
+ assert(!output_device);
+
+ // atomically find a free stream.
+ cubeb_stream * stm = NULL;
+ unique_lock lock;
+ for (unsigned i = 0u; i < MAX_STREAMS; ++i) {
+ // This check is only an optimization, we don't strictly need it
+ // since we check again after locking the mutex.
+ if (ctx->streams[i].in_use.load()) {
+ continue;
+ }
+
+ // if this fails, another thread initialized this stream
+ // between our check of in_use and this.
+ lock = unique_lock(ctx->streams[i].mutex, std::try_to_lock);
+ if (!lock.owns_lock()) {
+ continue;
+ }
+
+ if (ctx->streams[i].in_use.load()) {
+ lock = {};
+ continue;
+ }
+
+ stm = &ctx->streams[i];
+ break;
+ }
+
+ if (!stm) {
+ LOG("Error: maximum number of streams reached");
+ return CUBEB_ERROR;
+ }
+
+ stm->context = ctx;
+ stm->user_ptr = user_ptr;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->voice_input = input_stream_params &&
+ !!(input_stream_params->prefs & CUBEB_STREAM_PREF_VOICE);
+ stm->voice_output = output_stream_params &&
+ !!(output_stream_params->prefs & CUBEB_STREAM_PREF_VOICE);
+ stm->previous_clock = 0;
+
+ LOG("cubeb stream prefs: voice_input: %s voice_output: %s",
+ stm->voice_input ? "true" : "false",
+ stm->voice_output ? "true" : "false");
+
+ int err = aaudio_stream_init_impl(stm, input_device, input_stream_params,
+ output_device, output_stream_params,
+ latency_frames);
+ if (err != CUBEB_OK) {
+ // This is needed since aaudio_stream_destroy will lock the mutex again.
+ // It's no problem that there is a gap in between as the stream isn't
+ // actually in u se.
+ lock.unlock();
+ aaudio_stream_destroy(stm);
+ return err;
+ }
+
+ *stream = stm;
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_start(cubeb_stream * stm)
+{
+ assert(stm && stm->in_use.load());
+ lock_guard lock(stm->mutex);
+
+ stream_state state = stm->state.load();
+ int istate = stm->istream ? WRAP(AAudioStream_getState)(stm->istream) : 0;
+ int ostate = stm->ostream ? WRAP(AAudioStream_getState)(stm->ostream) : 0;
+ LOGV("STARTING stream %p: %d (%d %d)", (void *)stm, state, istate, ostate);
+
+ switch (state) {
+ case stream_state::STARTED:
+ case stream_state::STARTING:
+ LOG("cubeb stream %p already STARTING/STARTED", (void *)stm);
+ return CUBEB_OK;
+ case stream_state::ERROR:
+ case stream_state::SHUTDOWN:
+ return CUBEB_ERROR;
+ case stream_state::INIT:
+ assert(false && "Invalid stream");
+ return CUBEB_ERROR;
+ case stream_state::STOPPED:
+ case stream_state::STOPPING:
+ case stream_state::DRAINING:
+ break;
+ }
+
+ aaudio_result_t res;
+
+ // Important to start istream before ostream.
+ // As soon as we start ostream, the callbacks might be triggered an we
+ // might read from istream (on duplex). If istream wasn't started yet
+ // this is a problem.
+ if (stm->istream) {
+ res = WRAP(AAudioStream_requestStart)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStart (istream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->ostream) {
+ res = WRAP(AAudioStream_requestStart)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStart (ostream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ int ret = CUBEB_OK;
+ bool success;
+
+ while (!(success = stm->state.compare_exchange_strong(
+ state, stream_state::STARTING))) {
+ // we land here only if the state has changed in the meantime
+ switch (state) {
+ // If an error ocurred in the meantime, we can't change that.
+ // The stream will be stopped when shut down.
+ case stream_state::ERROR:
+ ret = CUBEB_ERROR;
+ break;
+ // The only situation in which the state could have switched to draining
+ // is if the callback was already fired and requested draining. Don't
+ // overwrite that. It's not an error either though.
+ case stream_state::DRAINING:
+ break;
+
+ // If the state switched [DRAINING -> STOPPING] or [DRAINING/STOPPING ->
+ // STOPPED] in the meantime, we can simply overwrite that since we restarted
+ // the stream.
+ case stream_state::STOPPING:
+ case stream_state::STOPPED:
+ continue;
+
+ // There is no situation in which the state could have been valid before
+ // but now in shutdown mode, since we hold the streams mutex.
+ // There is also no way that it switched *into* STARTING or
+ // STARTED mode.
+ default:
+ assert(false && "Invalid state change");
+ ret = CUBEB_ERROR;
+ break;
+ }
+
+ break;
+ }
+
+ if (success) {
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+ }
+
+ return ret;
+}
+
+static int
+aaudio_stream_stop(cubeb_stream * stm)
+{
+ assert(stm && stm->in_use.load());
+ lock_guard lock(stm->mutex);
+
+ stream_state state = stm->state.load();
+ int istate = stm->istream ? WRAP(AAudioStream_getState)(stm->istream) : 0;
+ int ostate = stm->ostream ? WRAP(AAudioStream_getState)(stm->ostream) : 0;
+ LOGV("STOPPING stream %p: %d (%d %d)", (void *)stm, state, istate, ostate);
+
+ switch (state) {
+ case stream_state::STOPPED:
+ case stream_state::STOPPING:
+ case stream_state::DRAINING:
+ LOG("cubeb stream %p already STOPPING/STOPPED", (void *)stm);
+ return CUBEB_OK;
+ case stream_state::ERROR:
+ case stream_state::SHUTDOWN:
+ return CUBEB_ERROR;
+ case stream_state::INIT:
+ assert(false && "Invalid stream");
+ return CUBEB_ERROR;
+ case stream_state::STARTED:
+ case stream_state::STARTING:
+ break;
+ }
+
+ aaudio_result_t res;
+
+ // No callbacks are triggered anymore when requestStop returns.
+ // That is important as we otherwise might read from a closed istream
+ // for a duplex stream.
+ // Therefor it is important to close ostream first.
+ if (stm->ostream) {
+ // Could use pause + flush here as well, the public cubeb interface
+ // doesn't state behavior.
+ res = WRAP(AAudioStream_requestStop)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop (ostream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->istream) {
+ res = WRAP(AAudioStream_requestStop)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop (istream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ int ret = CUBEB_OK;
+ bool success;
+ while (!(success = atomic_compare_exchange_strong(&stm->state, &state,
+ stream_state::STOPPING))) {
+ // we land here only if the state has changed in the meantime
+ switch (state) {
+ // If an error ocurred in the meantime, we can't change that.
+ // The stream will be STOPPED when shut down.
+ case stream_state::ERROR:
+ ret = CUBEB_ERROR;
+ break;
+ // If it was switched to DRAINING in the meantime, it was or
+ // will be STOPPED soon anyways. We don't interfere with
+ // the DRAINING process, no matter in which state.
+ // Not an error
+ case stream_state::DRAINING:
+ case stream_state::STOPPING:
+ case stream_state::STOPPED:
+ break;
+
+ // If the state switched from STARTING to STARTED in the meantime
+ // we can simply overwrite that since we just STOPPED it.
+ case stream_state::STARTED:
+ continue;
+
+ // There is no situation in which the state could have been valid before
+ // but now in shutdown mode, since we hold the streams mutex.
+ // There is also no way that it switched *into* STARTING mode.
+ default:
+ assert(false && "Invalid state change");
+ ret = CUBEB_ERROR;
+ break;
+ }
+
+ break;
+ }
+
+ if (success) {
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+ }
+
+ return ret;
+}
+
+static int
+aaudio_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ assert(stm && stm->in_use.load());
+ lock_guard lock(stm->mutex);
+
+ stream_state state = stm->state.load();
+ AAudioStream * stream = stm->ostream ? stm->ostream : stm->istream;
+ switch (state) {
+ case stream_state::ERROR:
+ case stream_state::SHUTDOWN:
+ return CUBEB_ERROR;
+ case stream_state::DRAINING:
+ case stream_state::STOPPED:
+ case stream_state::STOPPING:
+ // getTimestamp is only valid when the stream is playing.
+ // Simply return the number of frames passed to aaudio
+ *position = WRAP(AAudioStream_getFramesRead)(stream);
+ if (*position < stm->previous_clock) {
+ *position = stm->previous_clock;
+ } else {
+ stm->previous_clock = *position;
+ }
+ return CUBEB_OK;
+ case stream_state::INIT:
+ assert(false && "Invalid stream");
+ return CUBEB_ERROR;
+ case stream_state::STARTED:
+ case stream_state::STARTING:
+ break;
+ }
+
+ int64_t pos;
+ int64_t ns;
+ aaudio_result_t res;
+ res = WRAP(AAudioStream_getTimestamp)(stream, CLOCK_MONOTONIC, &pos, &ns);
+ if (res != AAUDIO_OK) {
+ // When the audio stream is not running, invalid_state is returned and we
+ // simply fall back to the method we use for non-playing streams.
+ if (res == AAUDIO_ERROR_INVALID_STATE) {
+ *position = WRAP(AAudioStream_getFramesRead)(stream);
+ if (*position < stm->previous_clock) {
+ *position = stm->previous_clock;
+ } else {
+ stm->previous_clock = *position;
+ }
+ return CUBEB_OK;
+ }
+
+ LOG("AAudioStream_getTimestamp: %s", WRAP(AAudio_convertResultToText)(res));
+ return CUBEB_ERROR;
+ }
+
+ *position = pos;
+ if (*position < stm->previous_clock) {
+ *position = stm->previous_clock;
+ } else {
+ stm->previous_clock = *position;
+ }
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ int64_t pos;
+ int64_t ns;
+ aaudio_result_t res;
+
+ if (!stm->ostream) {
+ LOG("error: aaudio_stream_get_latency on input-only stream");
+ return CUBEB_ERROR;
+ }
+
+ res =
+ WRAP(AAudioStream_getTimestamp)(stm->ostream, CLOCK_MONOTONIC, &pos, &ns);
+ if (res != AAUDIO_OK) {
+ LOG("aaudio_stream_get_latency, AAudioStream_getTimestamp: %s, returning "
+ "memoized value",
+ WRAP(AAudio_convertResultToText)(res));
+ // Expected when the stream is paused.
+ *latency = stm->latest_output_latency;
+ return CUBEB_OK;
+ }
+
+ int64_t read = WRAP(AAudioStream_getFramesRead)(stm->ostream);
+
+ *latency = stm->latest_output_latency = read - pos;
+ LOG("aaudio_stream_get_latency, %u", *latency);
+
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ int64_t pos;
+ int64_t ns;
+ aaudio_result_t res;
+
+ if (!stm->istream) {
+ LOG("error: aaudio_stream_get_input_latency on an ouput-only stream");
+ return CUBEB_ERROR;
+ }
+
+ res =
+ WRAP(AAudioStream_getTimestamp)(stm->istream, CLOCK_MONOTONIC, &pos, &ns);
+ if (res != AAUDIO_OK) {
+ // Expected when the stream is paused.
+ LOG("aaudio_stream_get_input_latency, AAudioStream_getTimestamp: %s, "
+ "returning memoized value",
+ WRAP(AAudio_convertResultToText)(res));
+ *latency = stm->latest_input_latency;
+ return CUBEB_OK;
+ }
+
+ int64_t written = WRAP(AAudioStream_getFramesWritten)(stm->istream);
+
+ *latency = stm->latest_input_latency = written - pos;
+ LOG("aaudio_stream_get_input_latency, %u", *latency);
+
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ assert(stm && stm->in_use.load() && stm->ostream);
+ stm->volume.store(volume);
+ return CUBEB_OK;
+}
+
+aaudio_data_callback_result_t
+dummy_callback(AAudioStream * stream, void * userData, void * audioData,
+ int32_t numFrames)
+{
+ return AAUDIO_CALLBACK_RESULT_STOP;
+}
+
+// Returns a dummy stream with all default settings
+static AAudioStream *
+init_dummy_stream()
+{
+ AAudioStreamBuilder * streamBuilder;
+ aaudio_result_t res;
+ res = WRAP(AAudio_createStreamBuilder)(&streamBuilder);
+ if (res != AAUDIO_OK) {
+ LOG("init_dummy_stream: AAudio_createStreamBuilder: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return nullptr;
+ }
+ WRAP(AAudioStreamBuilder_setDataCallback)
+ (streamBuilder, dummy_callback, nullptr);
+ WRAP(AAudioStreamBuilder_setPerformanceMode)
+ (streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+
+ AAudioStream * stream;
+ res = WRAP(AAudioStreamBuilder_openStream)(streamBuilder, &stream);
+ if (res != AAUDIO_OK) {
+ LOG("init_dummy_stream: AAudioStreamBuilder_openStream %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return nullptr;
+ }
+ WRAP(AAudioStreamBuilder_delete)(streamBuilder);
+
+ return stream;
+}
+
+static void
+destroy_dummy_stream(AAudioStream * stream)
+{
+ WRAP(AAudioStream_close)(stream);
+}
+
+static int
+aaudio_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ AAudioStream * stream = init_dummy_stream();
+
+ if (!stream) {
+ return CUBEB_ERROR;
+ }
+
+ // https://android.googlesource.com/platform/compatibility/cdd/+/refs/heads/master/5_multimedia/5_6_audio-latency.md
+ *latency_frames = WRAP(AAudioStream_getFramesPerBurst)(stream);
+
+ LOG("aaudio_get_min_latency: %u frames", *latency_frames);
+
+ destroy_dummy_stream(stream);
+
+ return CUBEB_OK;
+}
+
+int
+aaudio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ AAudioStream * stream = init_dummy_stream();
+
+ if (!stream) {
+ return CUBEB_ERROR;
+ }
+
+ *rate = WRAP(AAudioStream_getSampleRate)(stream);
+
+ LOG("aaudio_get_preferred_sample_rate %uHz", *rate);
+
+ destroy_dummy_stream(stream);
+
+ return CUBEB_OK;
+}
+
+extern "C" int
+aaudio_init(cubeb ** context, char const * context_name);
+
+const static struct cubeb_ops aaudio_ops = {
+ /*.init =*/aaudio_init,
+ /*.get_backend_id =*/aaudio_get_backend_id,
+ /*.get_max_channel_count =*/aaudio_get_max_channel_count,
+ /* .get_min_latency =*/aaudio_get_min_latency,
+ /*.get_preferred_sample_rate =*/aaudio_get_preferred_sample_rate,
+ /*.enumerate_devices =*/NULL,
+ /*.device_collection_destroy =*/NULL,
+ /*.destroy =*/aaudio_destroy,
+ /*.stream_init =*/aaudio_stream_init,
+ /*.stream_destroy =*/aaudio_stream_destroy,
+ /*.stream_start =*/aaudio_stream_start,
+ /*.stream_stop =*/aaudio_stream_stop,
+ /*.stream_get_position =*/aaudio_stream_get_position,
+ /*.stream_get_latency =*/aaudio_stream_get_latency,
+ /*.stream_get_input_latency =*/aaudio_stream_get_input_latency,
+ /*.stream_set_volume =*/aaudio_stream_set_volume,
+ /*.stream_set_name =*/NULL,
+ /*.stream_get_current_device =*/NULL,
+ /*.stream_device_destroy =*/NULL,
+ /*.stream_register_device_changed_callback =*/NULL,
+ /*.register_device_collection_changed =*/NULL};
+
+extern "C" /*static*/ int
+aaudio_init(cubeb ** context, char const * /* context_name */)
+{
+ // load api
+ void * libaaudio = NULL;
+#ifndef DISABLE_LIBAAUDIO_DLOPEN
+ libaaudio = dlopen("libaaudio.so", RTLD_NOW);
+ if (!libaaudio) {
+ return CUBEB_ERROR;
+ }
+
+#define LOAD(x) \
+ { \
+ WRAP(x) = (decltype(WRAP(x)))(dlsym(libaaudio, #x)); \
+ if (!WRAP(x)) { \
+ LOG("AAudio: Failed to load %s", #x); \
+ dlclose(libaaudio); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBAAUDIO_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
+ cubeb * ctx = new cubeb;
+ ctx->ops = &aaudio_ops;
+ ctx->libaaudio = libaaudio;
+
+ ctx->state.thread = std::thread(state_thread, ctx);
+
+ // NOTE: using platform-specific APIs we could set the priority of the
+ // notifier thread lower than the priority of the state thread.
+ // This way, it's more likely that the state thread will be woken up
+ // by the condition variable signal when both are currently waiting
+ ctx->state.notifier = std::thread(notifier_thread, ctx);
+
+ *context = ctx;
+ return CUBEB_OK;
+}
diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c
index 72a6acfb1c..4a55b02317 100644
--- a/media/libcubeb/src/cubeb_alsa.c
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -8,15 +8,63 @@
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#define _XOPEN_SOURCE 500
-#include <pthread.h>
-#include <sys/time.h>
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include <alsa/asoundlib.h>
#include <assert.h>
+#include <dlfcn.h>
#include <limits.h>
#include <poll.h>
+#include <pthread.h>
+#include <sys/time.h>
#include <unistd.h>
-#include <alsa/asoundlib.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
+
+#ifdef DISABLE_LIBASOUND_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) cubeb_##x
+#define LIBASOUND_API_VISIT(X) \
+ X(snd_config) \
+ X(snd_config_add) \
+ X(snd_config_copy) \
+ X(snd_config_delete) \
+ X(snd_config_get_id) \
+ X(snd_config_get_string) \
+ X(snd_config_imake_integer) \
+ X(snd_config_search) \
+ X(snd_config_search_definition) \
+ X(snd_lib_error_set_handler) \
+ X(snd_pcm_avail_update) \
+ X(snd_pcm_close) \
+ X(snd_pcm_delay) \
+ X(snd_pcm_drain) \
+ X(snd_pcm_frames_to_bytes) \
+ X(snd_pcm_get_params) \
+ X(snd_pcm_hw_params_any) \
+ X(snd_pcm_hw_params_get_channels_max) \
+ X(snd_pcm_hw_params_get_rate) \
+ X(snd_pcm_hw_params_set_rate_near) \
+ X(snd_pcm_hw_params_sizeof) \
+ X(snd_pcm_nonblock) \
+ X(snd_pcm_open) \
+ X(snd_pcm_open_lconf) \
+ X(snd_pcm_pause) \
+ X(snd_pcm_poll_descriptors) \
+ X(snd_pcm_poll_descriptors_count) \
+ X(snd_pcm_poll_descriptors_revents) \
+ X(snd_pcm_readi) \
+ X(snd_pcm_recover) \
+ X(snd_pcm_set_params) \
+ X(snd_pcm_start) \
+ X(snd_pcm_state) \
+ X(snd_pcm_writei)
+
+#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
+LIBASOUND_API_VISIT(MAKE_TYPEDEF);
+#undef MAKE_TYPEDEF
+/* snd_pcm_hw_params_alloca is actually a macro */
+#define snd_pcm_hw_params_sizeof cubeb_snd_pcm_hw_params_sizeof
+#endif
#define CUBEB_STREAM_MAX 16
#define CUBEB_WATCHDOG_MS 10000
@@ -36,6 +84,7 @@ static struct cubeb_ops const alsa_ops;
struct cubeb {
struct cubeb_ops const * ops;
+ void * libasound;
pthread_t thread;
@@ -52,7 +101,8 @@ struct cubeb {
int shutdown;
- /* Control pipe for forcing poll to wake and rebuild fds or recalculate the timeout. */
+ /* Control pipe for forcing poll to wake and rebuild fds or recalculate the
+ * timeout. */
int control_fd_read;
int control_fd_write;
@@ -67,22 +117,18 @@ struct cubeb {
int is_pa;
};
-enum stream_state {
- INACTIVE,
- RUNNING,
- DRAINING,
- PROCESSING,
- ERROR
-};
+enum stream_state { INACTIVE, RUNNING, DRAINING, PROCESSING, ERROR };
struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
+ void * user_ptr;
+ /**/
pthread_mutex_t mutex;
snd_pcm_t * pcm;
cubeb_data_callback data_callback;
cubeb_state_callback state_callback;
- void * user_ptr;
- snd_pcm_uframes_t write_position;
+ snd_pcm_uframes_t stream_position;
snd_pcm_uframes_t last_position;
snd_pcm_uframes_t buffer_size;
cubeb_stream_params params;
@@ -95,7 +141,8 @@ struct cubeb_stream {
enum stream_state state;
struct pollfd * saved_fds; /* A copy of the pollfds passed in at init time. */
- struct pollfd * fds; /* Pointer to this waitable's pollfds within struct cubeb's fds. */
+ struct pollfd *
+ fds; /* Pointer to this waitable's pollfds within struct cubeb's fds. */
nfds_t nfds;
struct timeval drain_timeout;
@@ -107,6 +154,12 @@ struct cubeb_stream {
being logically active and playing. */
struct timeval last_activity;
float volume;
+
+ char * buffer;
+ snd_pcm_uframes_t bufframes;
+ snd_pcm_stream_t stream_type;
+
+ struct cubeb_stream * other_stream;
};
static int
@@ -235,6 +288,16 @@ set_timeout(struct timeval * timeout, unsigned int ms)
}
static void
+stream_buffer_decrement(cubeb_stream * stm, long count)
+{
+ char * bufremains =
+ stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count);
+ memmove(stm->buffer, bufremains,
+ WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count));
+ stm->bufframes -= count;
+}
+
+static void
alsa_set_stream_state(cubeb_stream * stm, enum stream_state state)
{
cubeb * ctx;
@@ -249,97 +312,185 @@ alsa_set_stream_state(cubeb_stream * stm, enum stream_state state)
}
static enum stream_state
-alsa_refill_stream(cubeb_stream * stm)
+alsa_process_stream(cubeb_stream * stm)
{
+ unsigned short revents;
snd_pcm_sframes_t avail;
- long got;
- void * p;
int draining;
draining = 0;
pthread_mutex_lock(&stm->mutex);
- avail = snd_pcm_avail_update(stm->pcm);
- if (avail < 0) {
- snd_pcm_recover(stm->pcm, avail, 1);
- avail = snd_pcm_avail_update(stm->pcm);
- }
+ /* Call _poll_descriptors_revents() even if we don't use it
+ to let underlying plugins clear null events. Otherwise poll()
+ may wake up again and again, producing unnecessary CPU usage. */
+ WRAP(snd_pcm_poll_descriptors_revents)
+ (stm->pcm, stm->fds, stm->nfds, &revents);
- /* Failed to recover from an xrun, this stream must be broken. */
- if (avail < 0) {
+ avail = WRAP(snd_pcm_avail_update)(stm->pcm);
+
+ /* Got null event? Bail and wait for another wakeup. */
+ if (avail == 0) {
pthread_mutex_unlock(&stm->mutex);
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- return ERROR;
+ return RUNNING;
}
- /* This should never happen. */
- if ((unsigned int) avail > stm->buffer_size) {
+ /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time.
+ */
+ if ((unsigned int)avail > stm->buffer_size) {
avail = stm->buffer_size;
}
- /* poll(2) claims this stream is active, so there should be some space
- available to write. If avail is still zero here, the stream must be in
- a funky state, bail and wait for another wakeup. */
- if (avail == 0) {
+ /* Capture: Read available frames */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE && avail > 0) {
+ snd_pcm_sframes_t got;
+
+ if (avail + stm->bufframes > stm->buffer_size) {
+ /* Buffer overflow. Skip and overwrite with new data. */
+ stm->bufframes = 0;
+ // TODO: should it be marked as DRAINING?
+ }
+
+ got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer + stm->bufframes, avail);
+
+ if (got < 0) {
+ avail = got; // the error handler below will recover us
+ } else {
+ stm->bufframes += got;
+ stm->stream_position += got;
+
+ gettimeofday(&stm->last_activity, NULL);
+ }
+ }
+
+ /* Capture: Pass read frames to callback function */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 &&
+ (!stm->other_stream ||
+ stm->other_stream->bufframes < stm->other_stream->buffer_size)) {
+ snd_pcm_sframes_t wrote = stm->bufframes;
+ struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm;
+ void * other_buffer = stm->other_stream ? stm->other_stream->buffer +
+ stm->other_stream->bufframes
+ : NULL;
+
+ /* Correct write size to the other stream available space */
+ if (stm->other_stream &&
+ wrote > (snd_pcm_sframes_t)(stm->other_stream->buffer_size -
+ stm->other_stream->bufframes)) {
+ wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes;
+ }
+
pthread_mutex_unlock(&stm->mutex);
- return RUNNING;
+ wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer,
+ other_buffer, wrote);
+ pthread_mutex_lock(&stm->mutex);
+
+ if (wrote < 0) {
+ avail = wrote; // the error handler below will recover us
+ } else {
+ stream_buffer_decrement(stm, wrote);
+
+ if (stm->other_stream) {
+ stm->other_stream->bufframes += wrote;
+ }
+ }
}
- p = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, avail));
- assert(p);
+ /* Playback: Don't have enough data? Let's ask for more. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK &&
+ avail > (snd_pcm_sframes_t)stm->bufframes &&
+ (!stm->other_stream || stm->other_stream->bufframes > 0)) {
+ long got = avail - stm->bufframes;
+ void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL;
+ char * buftail =
+ stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
+
+ /* Correct read size to the other stream available frames */
+ if (stm->other_stream &&
+ got > (snd_pcm_sframes_t)stm->other_stream->bufframes) {
+ got = stm->other_stream->bufframes;
+ }
- pthread_mutex_unlock(&stm->mutex);
- got = stm->data_callback(stm, stm->user_ptr, NULL, p, avail);
- pthread_mutex_lock(&stm->mutex);
- if (got < 0) {
pthread_mutex_unlock(&stm->mutex);
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- free(p);
- return ERROR;
+ got = stm->data_callback(stm, stm->user_ptr, other_buffer, buftail, got);
+ pthread_mutex_lock(&stm->mutex);
+
+ if (got < 0) {
+ avail = got; // the error handler below will recover us
+ } else {
+ stm->bufframes += got;
+
+ if (stm->other_stream) {
+ stream_buffer_decrement(stm->other_stream, got);
+ }
+ }
+ }
+
+ /* Playback: Still don't have enough data? Add some silence. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK &&
+ avail > (snd_pcm_sframes_t)stm->bufframes) {
+ long drain_frames = avail - stm->bufframes;
+ double drain_time = (double)drain_frames / stm->params.rate;
+
+ char * buftail =
+ stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
+ memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames));
+ stm->bufframes = avail;
+
+ /* Mark as draining, unless we're waiting for capture */
+ if (!stm->other_stream || stm->other_stream->bufframes > 0) {
+ set_timeout(&stm->drain_timeout, drain_time * 1000);
+
+ draining = 1;
+ }
}
- if (got > 0) {
+
+ /* Playback: Have enough data and no errors. Let's write it out. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > 0) {
snd_pcm_sframes_t wrote;
if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
- float * b = (float *) p;
- for (uint32_t i = 0; i < got * stm->params.channels; i++) {
+ float * b = (float *)stm->buffer;
+ for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
b[i] *= stm->volume;
}
} else {
- short * b = (short *) p;
- for (uint32_t i = 0; i < got * stm->params.channels; i++) {
+ short * b = (short *)stm->buffer;
+ for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
b[i] *= stm->volume;
}
}
- wrote = snd_pcm_writei(stm->pcm, p, got);
+
+ wrote = WRAP(snd_pcm_writei)(stm->pcm, stm->buffer, avail);
if (wrote < 0) {
- snd_pcm_recover(stm->pcm, wrote, 1);
- wrote = snd_pcm_writei(stm->pcm, p, got);
- }
- if (wrote < 0 || wrote != got) {
- /* Recovery failed, somehow. */
- pthread_mutex_unlock(&stm->mutex);
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- return ERROR;
+ avail = wrote; // the error handler below will recover us
+ } else {
+ stream_buffer_decrement(stm, wrote);
+
+ stm->stream_position += wrote;
+ gettimeofday(&stm->last_activity, NULL);
}
- stm->write_position += wrote;
- gettimeofday(&stm->last_activity, NULL);
}
- if (got != avail) {
- long buffer_fill = stm->buffer_size - (avail - got);
- double buffer_time = (double) buffer_fill / stm->params.rate;
- /* Fill the remaining buffer with silence to guarantee one full period
- has been written. */
- snd_pcm_writei(stm->pcm, (char *) p + got, avail - got);
+ /* Got some error? Let's try to recover the stream. */
+ if (avail < 0) {
+ avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0);
- set_timeout(&stm->drain_timeout, buffer_time * 1000);
+ /* Capture pcm must be started after initial setup/recover */
+ if (avail >= 0 && stm->stream_type == SND_PCM_STREAM_CAPTURE &&
+ WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
+ avail = WRAP(snd_pcm_start)(stm->pcm);
+ }
+ }
- draining = 1;
+ /* Failed to recover, this stream must be broken. */
+ if (avail < 0) {
+ pthread_mutex_unlock(&stm->mutex);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return ERROR;
}
- free(p);
pthread_mutex_unlock(&stm->mutex);
return draining ? DRAINING : RUNNING;
}
@@ -392,10 +543,11 @@ alsa_run(cubeb * ctx)
stm = ctx->streams[i];
/* We can't use snd_pcm_poll_descriptors_revents here because of
https://github.com/kinetiknz/cubeb/issues/135. */
- if (stm && stm->state == RUNNING && stm->fds && any_revents(stm->fds, stm->nfds)) {
+ if (stm && stm->state == RUNNING && stm->fds &&
+ any_revents(stm->fds, stm->nfds)) {
alsa_set_stream_state(stm, PROCESSING);
pthread_mutex_unlock(&ctx->mutex);
- state = alsa_refill_stream(stm);
+ state = alsa_process_stream(stm);
pthread_mutex_lock(&ctx->mutex);
alsa_set_stream_state(stm, state);
}
@@ -407,7 +559,8 @@ alsa_run(cubeb * ctx)
if (stm->state == DRAINING && ms_since(&stm->drain_timeout) >= 0) {
alsa_set_stream_state(stm, INACTIVE);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
- } else if (stm->state == RUNNING && ms_since(&stm->last_activity) > CUBEB_WATCHDOG_MS) {
+ } else if (stm->state == RUNNING &&
+ ms_since(&stm->last_activity) > CUBEB_WATCHDOG_MS) {
alsa_set_stream_state(stm, ERROR);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
}
@@ -445,35 +598,36 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
slave_def = NULL;
- r = snd_config_search(root_pcm, "slave", &slave_pcm);
+ r = WRAP(snd_config_search)(root_pcm, "slave", &slave_pcm);
if (r < 0) {
return NULL;
}
- r = snd_config_get_string(slave_pcm, &string);
+ r = WRAP(snd_config_get_string)(slave_pcm, &string);
if (r >= 0) {
- r = snd_config_search_definition(lconf, "pcm_slave", string, &slave_def);
+ r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string,
+ &slave_def);
if (r < 0) {
return NULL;
}
}
do {
- r = snd_config_search(slave_def ? slave_def : slave_pcm, "pcm", &pcm);
+ r = WRAP(snd_config_search)(slave_def ? slave_def : slave_pcm, "pcm", &pcm);
if (r < 0) {
break;
}
- r = snd_config_get_string(slave_def ? slave_def : slave_pcm, &string);
+ r = WRAP(snd_config_get_string)(slave_def ? slave_def : slave_pcm, &string);
if (r < 0) {
break;
}
r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
- if (r < 0 || r > (int) sizeof(node_name)) {
+ if (r < 0 || r > (int)sizeof(node_name)) {
break;
}
- r = snd_config_search(lconf, node_name, &pcm);
+ r = WRAP(snd_config_search)(lconf, node_name, &pcm);
if (r < 0) {
break;
}
@@ -482,7 +636,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
} while (0);
if (slave_def) {
- snd_config_delete(slave_def);
+ WRAP(snd_config_delete)(slave_def);
}
return NULL;
@@ -492,7 +646,8 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
higher than requested latency, but the plugin does not update its (and
ALSA's) internal state to reflect that, leading to an immediate underrun
situation. Inspired by WINE's make_handle_underrun_config.
- Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05 */
+ Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05
+ */
static snd_config_t *
init_local_config_with_workaround(char const * pcm_name)
{
@@ -505,47 +660,49 @@ init_local_config_with_workaround(char const * pcm_name)
lconf = NULL;
- if (snd_config == NULL) {
+ if (*WRAP(snd_config) == NULL) {
return NULL;
}
- r = snd_config_copy(&lconf, snd_config);
+ r = WRAP(snd_config_copy)(&lconf, *WRAP(snd_config));
if (r < 0) {
return NULL;
}
do {
- r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node);
+ r = WRAP(snd_config_search_definition)(lconf, "pcm", pcm_name, &pcm_node);
if (r < 0) {
break;
}
- r = snd_config_get_id(pcm_node, &string);
+ r = WRAP(snd_config_get_id)(pcm_node, &string);
if (r < 0) {
break;
}
r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
- if (r < 0 || r > (int) sizeof(node_name)) {
+ if (r < 0 || r > (int)sizeof(node_name)) {
break;
}
- r = snd_config_search(lconf, node_name, &pcm_node);
+ r = WRAP(snd_config_search)(lconf, node_name, &pcm_node);
if (r < 0) {
break;
}
- /* If this PCM has a slave, walk the slave configurations until we reach the bottom. */
+ /* If this PCM has a slave, walk the slave configurations until we reach the
+ * bottom. */
while ((node = get_slave_pcm_node(lconf, pcm_node)) != NULL) {
pcm_node = node;
}
- /* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */
- r = snd_config_search(pcm_node, "type", &node);
+ /* Fetch the PCM node's type, and bail out if it's not the PulseAudio
+ * plugin. */
+ r = WRAP(snd_config_search)(pcm_node, "type", &node);
if (r < 0) {
break;
}
- r = snd_config_get_string(node, &string);
+ r = WRAP(snd_config_get_string)(node, &string);
if (r < 0) {
break;
}
@@ -556,18 +713,18 @@ init_local_config_with_workaround(char const * pcm_name)
/* Don't clobber an explicit existing handle_underrun value, set it only
if it doesn't already exist. */
- r = snd_config_search(pcm_node, "handle_underrun", &node);
+ r = WRAP(snd_config_search)(pcm_node, "handle_underrun", &node);
if (r != -ENOENT) {
break;
}
/* Disable pcm_pulse's asynchronous underrun handling. */
- r = snd_config_imake_integer(&node, "handle_underrun", 0);
+ r = WRAP(snd_config_imake_integer)(&node, "handle_underrun", 0);
if (r < 0) {
break;
}
- r = snd_config_add(pcm_node, node);
+ r = WRAP(snd_config_add)(pcm_node, node);
if (r < 0) {
break;
}
@@ -575,21 +732,23 @@ init_local_config_with_workaround(char const * pcm_name)
return lconf;
} while (0);
- snd_config_delete(lconf);
+ WRAP(snd_config_delete)(lconf);
return NULL;
}
static int
-alsa_locked_pcm_open(snd_pcm_t ** pcm, snd_pcm_stream_t stream, snd_config_t * local_config)
+alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name,
+ snd_pcm_stream_t stream, snd_config_t * local_config)
{
int r;
pthread_mutex_lock(&cubeb_alsa_mutex);
if (local_config) {
- r = snd_pcm_open_lconf(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK, local_config);
+ r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK,
+ local_config);
} else {
- r = snd_pcm_open(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK);
+ r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK);
}
pthread_mutex_unlock(&cubeb_alsa_mutex);
@@ -602,7 +761,7 @@ alsa_locked_pcm_close(snd_pcm_t * pcm)
int r;
pthread_mutex_lock(&cubeb_alsa_mutex);
- r = snd_pcm_close(pcm);
+ r = WRAP(snd_pcm_close)(pcm);
pthread_mutex_unlock(&cubeb_alsa_mutex);
return r;
@@ -658,6 +817,7 @@ silent_error_handler(char const * file, int line, char const * function,
alsa_init(cubeb ** context, char const * context_name)
{
(void)context_name;
+ void * libasound = NULL;
cubeb * ctx;
int r;
int i;
@@ -668,9 +828,31 @@ alsa_init(cubeb ** context, char const * context_name)
assert(context);
*context = NULL;
+#ifndef DISABLE_LIBASOUND_DLOPEN
+ libasound = dlopen("libasound.so.2", RTLD_LAZY);
+ if (!libasound) {
+ libasound = dlopen("libasound.so", RTLD_LAZY);
+ if (!libasound) {
+ return CUBEB_ERROR;
+ }
+ }
+
+#define LOAD(x) \
+ { \
+ cubeb_##x = dlsym(libasound, #x); \
+ if (!cubeb_##x) { \
+ dlclose(libasound); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBASOUND_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
pthread_mutex_lock(&cubeb_alsa_mutex);
if (!cubeb_alsa_error_handler_set) {
- snd_lib_error_set_handler(silent_error_handler);
+ WRAP(snd_lib_error_set_handler)(silent_error_handler);
cubeb_alsa_error_handler_set = 1;
}
pthread_mutex_unlock(&cubeb_alsa_mutex);
@@ -679,6 +861,7 @@ alsa_init(cubeb ** context, char const * context_name)
assert(ctx);
ctx->ops = &alsa_ops;
+ ctx->libasound = libasound;
r = pthread_mutex_init(&ctx->mutex, NULL);
assert(r == 0);
@@ -712,7 +895,8 @@ alsa_init(cubeb ** context, char const * context_name)
/* Open a dummy PCM to force the configuration space to be evaluated so that
init_local_config_with_workaround can find and modify the default node. */
- r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, NULL);
+ r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK,
+ NULL);
if (r >= 0) {
alsa_locked_pcm_close(dummy);
}
@@ -722,12 +906,13 @@ alsa_init(cubeb ** context, char const * context_name)
pthread_mutex_unlock(&cubeb_alsa_mutex);
if (ctx->local_config) {
ctx->is_pa = 1;
- r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
+ r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME,
+ SND_PCM_STREAM_PLAYBACK, ctx->local_config);
/* If we got a local_config, we found a PA PCM. If opening a PCM with that
config fails with EINVAL, the PA PCM is too old for this workaround. */
if (r == -EINVAL) {
pthread_mutex_lock(&cubeb_alsa_mutex);
- snd_config_delete(ctx->local_config);
+ WRAP(snd_config_delete)(ctx->local_config);
pthread_mutex_unlock(&cubeb_alsa_mutex);
ctx->local_config = NULL;
} else if (r >= 0) {
@@ -769,24 +954,28 @@ alsa_destroy(cubeb * ctx)
if (ctx->local_config) {
pthread_mutex_lock(&cubeb_alsa_mutex);
- snd_config_delete(ctx->local_config);
+ WRAP(snd_config_delete)(ctx->local_config);
pthread_mutex_unlock(&cubeb_alsa_mutex);
}
+ if (ctx->libasound) {
+ dlclose(ctx->libasound);
+ }
+
free(ctx);
}
-static void alsa_stream_destroy(cubeb_stream * stm);
+static void
+alsa_stream_destroy(cubeb_stream * stm);
static int
-alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback, cubeb_state_callback state_callback,
- void * user_ptr)
+alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream,
+ char const * stream_name, snd_pcm_stream_t stream_type,
+ cubeb_devid deviceid,
+ cubeb_stream_params * stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
{
(void)stream_name;
cubeb_stream * stm;
@@ -794,23 +983,18 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
snd_pcm_format_t format;
snd_pcm_uframes_t period_size;
int latency_us = 0;
-
+ char const * pcm_name =
+ deviceid ? (char const *)deviceid : CUBEB_ALSA_PCM_NAME;
assert(ctx && stream);
- if (input_stream_params) {
- /* Capture support not yet implemented. */
- return CUBEB_ERROR_NOT_SUPPORTED;
- }
+ *stream = NULL;
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
}
- *stream = NULL;
-
- switch (output_stream_params->format) {
+ switch (stream_params->format) {
case CUBEB_SAMPLE_S16LE:
format = SND_PCM_FORMAT_S16_LE;
break;
@@ -842,20 +1026,28 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
stm->data_callback = data_callback;
stm->state_callback = state_callback;
stm->user_ptr = user_ptr;
- stm->params = *output_stream_params;
+ stm->params = *stream_params;
stm->state = INACTIVE;
stm->volume = 1.0;
+ stm->buffer = NULL;
+ stm->bufframes = 0;
+ stm->stream_type = stream_type;
+ stm->other_stream = NULL;
r = pthread_mutex_init(&stm->mutex, NULL);
assert(r == 0);
- r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
+ r = pthread_cond_init(&stm->cond, NULL);
+ assert(r == 0);
+
+ r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type,
+ ctx->local_config);
if (r < 0) {
alsa_stream_destroy(stm);
return CUBEB_ERROR;
}
- r = snd_pcm_nonblock(stm->pcm, 1);
+ r = WRAP(snd_pcm_nonblock)(stm->pcm, 1);
assert(r == 0);
latency_us = latency_frames * 1e6 / stm->params.rate;
@@ -865,30 +1057,34 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
Only resort to this hack if the handle_underrun workaround failed. */
if (!ctx->local_config && ctx->is_pa) {
const int min_latency = 5e5;
- latency_us = latency_us < min_latency ? min_latency: latency_us;
+ latency_us = latency_us < min_latency ? min_latency : latency_us;
}
- r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
- stm->params.channels, stm->params.rate, 1,
- latency_us);
+ r = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
+ stm->params.channels, stm->params.rate, 1,
+ latency_us);
if (r < 0) {
alsa_stream_destroy(stm);
return CUBEB_ERROR_INVALID_FORMAT;
}
- r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size);
+ r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size);
assert(r == 0);
- stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm);
+ /* Double internal buffer size to have enough space when waiting for the other
+ * side of duplex connection */
+ stm->buffer_size *= 2;
+ stm->buffer =
+ calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size));
+ assert(stm->buffer);
+
+ stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm);
assert(stm->nfds > 0);
stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd));
assert(stm->saved_fds);
- r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds);
- assert((nfds_t) r == stm->nfds);
-
- r = pthread_cond_init(&stm->cond, NULL);
- assert(r == 0);
+ r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds);
+ assert((nfds_t)r == stm->nfds);
if (alsa_register_stream(ctx, stm) != 0) {
alsa_stream_destroy(stm);
@@ -900,22 +1096,66 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
return CUBEB_OK;
}
+static int
+alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
+ cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int result = CUBEB_OK;
+ cubeb_stream *instm = NULL, *outstm = NULL;
+
+ if (result == CUBEB_OK && input_stream_params) {
+ result = alsa_stream_init_single(ctx, &instm, stream_name,
+ SND_PCM_STREAM_CAPTURE, input_device,
+ input_stream_params, latency_frames,
+ data_callback, state_callback, user_ptr);
+ }
+
+ if (result == CUBEB_OK && output_stream_params) {
+ result = alsa_stream_init_single(ctx, &outstm, stream_name,
+ SND_PCM_STREAM_PLAYBACK, output_device,
+ output_stream_params, latency_frames,
+ data_callback, state_callback, user_ptr);
+ }
+
+ if (result == CUBEB_OK && input_stream_params && output_stream_params) {
+ instm->other_stream = outstm;
+ outstm->other_stream = instm;
+ }
+
+ if (result != CUBEB_OK && instm) {
+ alsa_stream_destroy(instm);
+ }
+
+ *stream = outstm ? outstm : instm;
+
+ return result;
+}
+
static void
alsa_stream_destroy(cubeb_stream * stm)
{
int r;
cubeb * ctx;
- assert(stm && (stm->state == INACTIVE ||
- stm->state == ERROR ||
+ assert(stm && (stm->state == INACTIVE || stm->state == ERROR ||
stm->state == DRAINING));
ctx = stm->context;
+ if (stm->other_stream) {
+ stm->other_stream->other_stream = NULL; // to stop infinite recursion
+ alsa_stream_destroy(stm->other_stream);
+ }
+
pthread_mutex_lock(&stm->mutex);
if (stm->pcm) {
if (stm->state == DRAINING) {
- snd_pcm_drain(stm->pcm);
+ WRAP(snd_pcm_drain)(stm->pcm);
}
alsa_locked_pcm_close(stm->pcm);
stm->pcm = NULL;
@@ -934,6 +1174,8 @@ alsa_stream_destroy(cubeb_stream * stm)
ctx->active_streams -= 1;
pthread_mutex_unlock(&ctx->mutex);
+ free(stm->buffer);
+
free(stm);
}
@@ -942,7 +1184,7 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{
int r;
cubeb_stream * stm;
- snd_pcm_hw_params_t* hw_params;
+ snd_pcm_hw_params_t * hw_params;
cubeb_stream_params params;
params.rate = 44100;
params.format = CUBEB_SAMPLE_FLOAT32NE;
@@ -952,17 +1194,20 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
assert(ctx);
- r = alsa_stream_init(ctx, &stm, "", NULL, NULL, NULL, &params, 100, NULL, NULL, NULL);
+ r = alsa_stream_init(ctx, &stm, "", NULL, NULL, NULL, &params, 100, NULL,
+ NULL, NULL);
if (r != CUBEB_OK) {
return CUBEB_ERROR;
}
- r = snd_pcm_hw_params_any(stm->pcm, hw_params);
+ assert(stm);
+
+ r = WRAP(snd_pcm_hw_params_any)(stm->pcm, hw_params);
if (r < 0) {
return CUBEB_ERROR;
}
- r = snd_pcm_hw_params_get_channels_max(hw_params, max_channels);
+ r = WRAP(snd_pcm_hw_params_get_channels_max)(hw_params, max_channels);
if (r < 0) {
return CUBEB_ERROR;
}
@@ -973,7 +1218,8 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
}
static int
-alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) {
+alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
(void)ctx;
int r, dir;
snd_pcm_t * pcm;
@@ -983,40 +1229,42 @@ alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) {
/* get a pcm, disabling resampling, so we get a rate the
* hardware/dmix/pulse/etc. supports. */
- r = snd_pcm_open(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE);
+ r = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK,
+ SND_PCM_NO_AUTO_RESAMPLE);
if (r < 0) {
return CUBEB_ERROR;
}
- r = snd_pcm_hw_params_any(pcm, hw_params);
+ r = WRAP(snd_pcm_hw_params_any)(pcm, hw_params);
if (r < 0) {
- snd_pcm_close(pcm);
+ WRAP(snd_pcm_close)(pcm);
return CUBEB_ERROR;
}
- r = snd_pcm_hw_params_get_rate(hw_params, rate, &dir);
+ r = WRAP(snd_pcm_hw_params_get_rate)(hw_params, rate, &dir);
if (r >= 0) {
/* There is a default rate: use it. */
- snd_pcm_close(pcm);
+ WRAP(snd_pcm_close)(pcm);
return CUBEB_OK;
}
/* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */
*rate = 44100;
- r = snd_pcm_hw_params_set_rate_near(pcm, hw_params, rate, NULL);
+ r = WRAP(snd_pcm_hw_params_set_rate_near)(pcm, hw_params, rate, NULL);
if (r < 0) {
- snd_pcm_close(pcm);
+ WRAP(snd_pcm_close)(pcm);
return CUBEB_ERROR;
}
- snd_pcm_close(pcm);
+ WRAP(snd_pcm_close)(pcm);
return CUBEB_OK;
}
static int
-alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
+alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
{
(void)ctx;
/* 40ms is found to be an acceptable minimum, even on a super low-end
@@ -1034,8 +1282,19 @@ alsa_stream_start(cubeb_stream * stm)
assert(stm);
ctx = stm->context;
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) {
+ int r = alsa_stream_start(stm->other_stream);
+ if (r != CUBEB_OK)
+ return r;
+ }
+
pthread_mutex_lock(&stm->mutex);
- snd_pcm_pause(stm->pcm, 0);
+ /* Capture pcm must be started after initial setup/recover */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE &&
+ WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
+ WRAP(snd_pcm_start)(stm->pcm);
+ }
+ WRAP(snd_pcm_pause)(stm->pcm, 0);
gettimeofday(&stm->last_activity, NULL);
pthread_mutex_unlock(&stm->mutex);
@@ -1059,6 +1318,12 @@ alsa_stream_stop(cubeb_stream * stm)
assert(stm);
ctx = stm->context;
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) {
+ int r = alsa_stream_stop(stm->other_stream);
+ if (r != CUBEB_OK)
+ return r;
+ }
+
pthread_mutex_lock(&ctx->mutex);
while (stm->state == PROCESSING) {
r = pthread_cond_wait(&stm->cond, &ctx->mutex);
@@ -1069,7 +1334,7 @@ alsa_stream_stop(cubeb_stream * stm)
pthread_mutex_unlock(&ctx->mutex);
pthread_mutex_lock(&stm->mutex);
- snd_pcm_pause(stm->pcm, 1);
+ WRAP(snd_pcm_pause)(stm->pcm, 1);
pthread_mutex_unlock(&stm->mutex);
return CUBEB_OK;
@@ -1085,8 +1350,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position)
pthread_mutex_lock(&stm->mutex);
delay = -1;
- if (snd_pcm_state(stm->pcm) != SND_PCM_STATE_RUNNING ||
- snd_pcm_delay(stm->pcm, &delay) != 0) {
+ if (WRAP(snd_pcm_state)(stm->pcm) != SND_PCM_STATE_RUNNING ||
+ WRAP(snd_pcm_delay)(stm->pcm, &delay) != 0) {
*position = stm->last_position;
pthread_mutex_unlock(&stm->mutex);
return CUBEB_OK;
@@ -1095,8 +1360,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position)
assert(delay >= 0);
*position = 0;
- if (stm->write_position >= (snd_pcm_uframes_t) delay) {
- *position = stm->write_position - delay;
+ if (stm->stream_position >= (snd_pcm_uframes_t)delay) {
+ *position = stm->stream_position - delay;
}
stm->last_position = *position;
@@ -1110,8 +1375,9 @@ alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
snd_pcm_sframes_t delay;
/* This function returns the delay in frames until a frame written using
- snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways. */
- if (snd_pcm_delay(stm->pcm, &delay)) {
+ snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways.
+ */
+ if (WRAP(snd_pcm_delay)(stm->pcm, &delay)) {
return CUBEB_ERROR;
}
@@ -1131,24 +1397,86 @@ alsa_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_OK;
}
+static int
+alsa_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ cubeb_device_info * device = NULL;
+
+ if (!context)
+ return CUBEB_ERROR;
+
+ uint32_t rate, max_channels;
+ int r;
+
+ r = alsa_get_preferred_sample_rate(context, &rate);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ r = alsa_get_max_channel_count(context, &max_channels);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ char const * a_name = "default";
+ device = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info));
+ assert(device);
+ if (!device)
+ return CUBEB_ERROR;
+
+ device->device_id = a_name;
+ device->devid = (cubeb_devid)device->device_id;
+ device->friendly_name = a_name;
+ device->group_id = a_name;
+ device->vendor_name = a_name;
+ device->type = type;
+ device->state = CUBEB_DEVICE_STATE_ENABLED;
+ device->preferred = CUBEB_DEVICE_PREF_ALL;
+ device->format = CUBEB_DEVICE_FMT_S16NE;
+ device->default_format = CUBEB_DEVICE_FMT_S16NE;
+ device->max_channels = max_channels;
+ device->min_rate = rate;
+ device->max_rate = rate;
+ device->default_rate = rate;
+ device->latency_lo = 0;
+ device->latency_hi = 0;
+
+ collection->device = device;
+ collection->count = 1;
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ assert(collection->count == 1);
+ (void)context;
+ free(collection->device);
+ return CUBEB_OK;
+}
+
static struct cubeb_ops const alsa_ops = {
- .init = alsa_init,
- .get_backend_id = alsa_get_backend_id,
- .get_max_channel_count = alsa_get_max_channel_count,
- .get_min_latency = alsa_get_min_latency,
- .get_preferred_sample_rate = alsa_get_preferred_sample_rate,
- .enumerate_devices = NULL,
- .destroy = alsa_destroy,
- .stream_init = alsa_stream_init,
- .stream_destroy = alsa_stream_destroy,
- .stream_start = alsa_stream_start,
- .stream_stop = alsa_stream_stop,
- .stream_get_position = alsa_stream_get_position,
- .stream_get_latency = alsa_stream_get_latency,
- .stream_set_volume = alsa_stream_set_volume,
- .stream_set_panning = NULL,
- .stream_get_current_device = NULL,
- .stream_device_destroy = NULL,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = NULL
-};
+ .init = alsa_init,
+ .get_backend_id = alsa_get_backend_id,
+ .get_max_channel_count = alsa_get_max_channel_count,
+ .get_min_latency = alsa_get_min_latency,
+ .get_preferred_sample_rate = alsa_get_preferred_sample_rate,
+ .enumerate_devices = alsa_enumerate_devices,
+ .device_collection_destroy = alsa_device_collection_destroy,
+ .destroy = alsa_destroy,
+ .stream_init = alsa_stream_init,
+ .stream_destroy = alsa_stream_destroy,
+ .stream_start = alsa_stream_start,
+ .stream_stop = alsa_stream_stop,
+ .stream_get_position = alsa_stream_get_position,
+ .stream_get_latency = alsa_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = alsa_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/media/libcubeb/src/cubeb_android.h b/media/libcubeb/src/cubeb_android.h
new file mode 100644
index 0000000000..c21a941ab5
--- /dev/null
+++ b/media/libcubeb/src/cubeb_android.h
@@ -0,0 +1,17 @@
+#ifndef CUBEB_ANDROID_H
+#define CUBEB_ANDROID_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+// If the latency requested is above this threshold, this stream is considered
+// intended for playback (vs. real-time). Tell Android it should favor saving
+// power over performance or latency.
+// This is around 100ms at 44100 or 48000
+const uint16_t POWERSAVE_LATENCY_FRAMES_THRESHOLD = 4000;
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // CUBEB_ANDROID_H
diff --git a/media/libcubeb/src/cubeb_array_queue.h b/media/libcubeb/src/cubeb_array_queue.h
new file mode 100644
index 0000000000..d6d9581325
--- /dev/null
+++ b/media/libcubeb/src/cubeb_array_queue.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_ARRAY_QUEUE_H
+#define CUBEB_ARRAY_QUEUE_H
+
+#include <assert.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct {
+ void ** buf;
+ size_t num;
+ size_t writePos;
+ size_t readPos;
+ pthread_mutex_t mutex;
+} array_queue;
+
+array_queue *
+array_queue_create(size_t num)
+{
+ assert(num != 0);
+ array_queue * new_queue = (array_queue *)calloc(1, sizeof(array_queue));
+ new_queue->buf = (void **)calloc(1, sizeof(void *) * num);
+ new_queue->readPos = 0;
+ new_queue->writePos = 0;
+ new_queue->num = num;
+
+ pthread_mutex_init(&new_queue->mutex, NULL);
+
+ return new_queue;
+}
+
+void
+array_queue_destroy(array_queue * aq)
+{
+ assert(aq);
+
+ free(aq->buf);
+ pthread_mutex_destroy(&aq->mutex);
+ free(aq);
+}
+
+int
+array_queue_push(array_queue * aq, void * item)
+{
+ assert(item);
+
+ pthread_mutex_lock(&aq->mutex);
+ int ret = -1;
+ if (aq->buf[aq->writePos % aq->num] == NULL) {
+ aq->buf[aq->writePos % aq->num] = item;
+ aq->writePos = (aq->writePos + 1) % aq->num;
+ ret = 0;
+ }
+ // else queue is full
+ pthread_mutex_unlock(&aq->mutex);
+ return ret;
+}
+
+void *
+array_queue_pop(array_queue * aq)
+{
+ pthread_mutex_lock(&aq->mutex);
+ void * value = aq->buf[aq->readPos % aq->num];
+ if (value) {
+ aq->buf[aq->readPos % aq->num] = NULL;
+ aq->readPos = (aq->readPos + 1) % aq->num;
+ }
+ pthread_mutex_unlock(&aq->mutex);
+ return value;
+}
+
+size_t
+array_queue_get_size(array_queue * aq)
+{
+ pthread_mutex_lock(&aq->mutex);
+ ssize_t r = aq->writePos - aq->readPos;
+ if (r < 0) {
+ r = aq->num + r;
+ assert(r >= 0);
+ }
+ pthread_mutex_unlock(&aq->mutex);
+ return (size_t)r;
+}
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // CUBE_ARRAY_QUEUE_H
diff --git a/media/libcubeb/src/cubeb_assert.h b/media/libcubeb/src/cubeb_assert.h
new file mode 100644
index 0000000000..d81a1eacaf
--- /dev/null
+++ b/media/libcubeb/src/cubeb_assert.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_ASSERT
+#define CUBEB_ASSERT
+
+#include <stdio.h>
+#include <stdlib.h>
+
+/**
+ * This allow using an external release assert method. This file should only
+ * export a function or macro called XASSERT that aborts the program.
+ */
+
+#define XASSERT(expr) \
+ do { \
+ if (!(expr)) { \
+ fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
+ abort(); \
+ } \
+ } while (0)
+
+#endif
diff --git a/media/libcubeb/src/cubeb_audiotrack.c b/media/libcubeb/src/cubeb_audiotrack.c
deleted file mode 100644
index fe2603405e..0000000000
--- a/media/libcubeb/src/cubeb_audiotrack.c
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * Copyright © 2013 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-#if !defined(NDEBUG)
-#define NDEBUG
-#endif
-#include <assert.h>
-#include <pthread.h>
-#include <stdlib.h>
-#include <time.h>
-#include <dlfcn.h>
-#include "android/log.h"
-
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-#include "android/audiotrack_definitions.h"
-
-#ifndef ALOG
-#if defined(DEBUG) || defined(FORCE_ALOG)
-#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb" , ## args)
-#else
-#define ALOG(args...)
-#endif
-#endif
-
-/**
- * A lot of bytes for safety. It should be possible to bring this down a bit. */
-#define SIZE_AUDIOTRACK_INSTANCE 256
-
-/**
- * call dlsym to get the symbol |mangled_name|, handle the error and store the
- * pointer in |pointer|. Because depending on Android version, we want different
- * symbols, not finding a symbol is not an error. */
-#define DLSYM_DLERROR(mangled_name, pointer, lib) \
- do { \
- pointer = dlsym(lib, mangled_name); \
- if (!pointer) { \
- ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \
- } else { \
- ALOG("%stm: OK", mangled_name); \
- } \
- } while(0);
-
-static struct cubeb_ops const audiotrack_ops;
-void audiotrack_destroy(cubeb * context);
-void audiotrack_stream_destroy(cubeb_stream * stream);
-
-struct AudioTrack {
- /* only available on ICS and later. The second int paramter is in fact of type audio_stream_type_t. */
- /* static */ status_t (*get_min_frame_count)(int* frame_count, int stream_type, uint32_t rate);
- /* if we have a recent ctor, but can't find the above symbol, we
- * can get the minimum frame count with this signature, and we are
- * running gingerbread. */
- /* static */ status_t (*get_min_frame_count_gingerbread)(int* frame_count, int stream_type, uint32_t rate);
- void* (*ctor)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int, int);
- void* (*dtor)(void* instance);
- void (*start)(void* instance);
- void (*pause)(void* instance);
- uint32_t (*latency)(void* instance);
- status_t (*check)(void* instance);
- status_t (*get_position)(void* instance, uint32_t* position);
- /* static */ int (*get_output_samplingrate)(int* samplerate, int stream);
- status_t (*set_marker_position)(void* instance, unsigned int);
- status_t (*set_volume)(void* instance, float left, float right);
-};
-
-struct cubeb {
- struct cubeb_ops const * ops;
- void * library;
- struct AudioTrack klass;
-};
-
-struct cubeb_stream {
- cubeb * context;
- cubeb_stream_params params;
- cubeb_data_callback data_callback;
- cubeb_state_callback state_callback;
- void * instance;
- void * user_ptr;
- /* Number of frames that have been passed to the AudioTrack callback */
- long unsigned written;
- int draining;
-};
-
-static void
-audiotrack_refill(int event, void* user, void* info)
-{
- cubeb_stream * stream = user;
- switch (event) {
- case EVENT_MORE_DATA: {
- long got = 0;
- struct Buffer * b = (struct Buffer*)info;
-
- if (stream->draining) {
- return;
- }
-
- got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw, b->frameCount);
-
- stream->written += got;
-
- if (got != (long)b->frameCount) {
- stream->draining = 1;
- /* set a marker so we are notified when the are done draining, that is,
- * when every frame has been played by android. */
- stream->context->klass.set_marker_position(stream->instance, stream->written);
- }
-
- break;
- }
- case EVENT_UNDERRUN:
- ALOG("underrun in cubeb backend.");
- break;
- case EVENT_LOOP_END:
- assert(0 && "We don't support the loop feature of audiotrack.");
- break;
- case EVENT_MARKER:
- assert(stream->draining);
- stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
- break;
- case EVENT_NEW_POS:
- assert(0 && "We don't support the setPositionUpdatePeriod feature of audiotrack.");
- break;
- case EVENT_BUFFER_END:
- assert(0 && "Should not happen.");
- break;
- }
-}
-
-/* We are running on gingerbread if we found the gingerbread signature for
- * getMinFrameCount */
-static int
-audiotrack_version_is_gingerbread(cubeb * ctx)
-{
- return ctx->klass.get_min_frame_count_gingerbread != NULL;
-}
-
-int
-audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * min_frame_count)
-{
- status_t status;
- /* Recent Android have a getMinFrameCount method. */
- if (!audiotrack_version_is_gingerbread(ctx)) {
- status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate);
- } else {
- status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate);
- }
- if (status != 0) {
- ALOG("error getting the min frame count");
- return CUBEB_ERROR;
- }
- return CUBEB_OK;
-}
-
-int
-audiotrack_init(cubeb ** context, char const * context_name)
-{
- cubeb * ctx;
- struct AudioTrack* c;
-
- assert(context);
- *context = NULL;
-
- ctx = calloc(1, sizeof(*ctx));
- assert(ctx);
-
- /* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android
- * 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on
- * the first call to a dlsym'ed function. Somehow this does not happen when
- * using only the name of the library. */
- ctx->library = dlopen("libmedia.so", RTLD_LAZY);
- if (!ctx->library) {
- ALOG("dlopen error: %s.", dlerror());
- free(ctx);
- return CUBEB_ERROR;
- }
-
- /* Recent Android first, then Gingerbread. */
- DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", ctx->klass.ctor, ctx->library);
- DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library);
-
- DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library);
- DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library);
-
- DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library);
-
- /* |getMinFrameCount| is available on gingerbread and ICS with different signatures. */
- DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", ctx->klass.get_min_frame_count, ctx->library);
- if (!ctx->klass.get_min_frame_count) {
- DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", ctx->klass.get_min_frame_count_gingerbread, ctx->library);
- }
-
- DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, ctx->library);
- DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, ctx->library);
- DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", ctx->klass.get_position, ctx->library);
- DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", ctx->klass.set_marker_position, ctx->library);
- DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume, ctx->library);
-
- /* check that we have a combination of symbol that makes sense */
- c = &ctx->klass;
- if(!(c->ctor &&
- c->dtor && c->latency && c->check &&
- /* at least one way to get the minimum frame count to request. */
- (c->get_min_frame_count ||
- c->get_min_frame_count_gingerbread) &&
- c->start && c->pause && c->get_position && c->set_marker_position)) {
- ALOG("Could not find all the symbols we need.");
- audiotrack_destroy(ctx);
- return CUBEB_ERROR;
- }
-
- ctx->ops = &audiotrack_ops;
-
- *context = ctx;
-
- return CUBEB_OK;
-}
-
-char const *
-audiotrack_get_backend_id(cubeb * context)
-{
- return "audiotrack";
-}
-
-static int
-audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
-{
- assert(ctx && max_channels);
-
- /* The android mixer handles up to two channels, see
- http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
- *max_channels = 2;
-
- return CUBEB_OK;
-}
-
-static int
-audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
-{
- /* We always use the lowest latency possible when using this backend (see
- * audiotrack_stream_init), so this value is not going to be used. */
- int r;
-
- r = audiotrack_get_min_frame_count(ctx, &params, (int *)latency_ms);
- if (r != CUBEB_OK) {
- return CUBEB_ERROR;
- }
-
- return CUBEB_OK;
-}
-
-static int
-audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
-{
- status_t r;
-
- r = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */);
-
- return r == 0 ? CUBEB_OK : CUBEB_ERROR;
-}
-
-void
-audiotrack_destroy(cubeb * context)
-{
- assert(context);
-
- dlclose(context->library);
-
- free(context);
-}
-
-int
-audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
-{
- cubeb_stream * stm;
- int32_t channels;
- uint32_t min_frame_count;
-
- assert(ctx && stream);
-
- assert(!input_stream_params && "not supported");
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
- }
-
- if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE ||
- output_stream_params->format == CUBEB_SAMPLE_FLOAT32BE) {
- return CUBEB_ERROR_INVALID_FORMAT;
- }
-
- if (audiotrack_get_min_frame_count(ctx, output_stream_params, (int *)&min_frame_count)) {
- return CUBEB_ERROR;
- }
-
- stm = calloc(1, sizeof(*stm));
- assert(stm);
-
- stm->context = ctx;
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
- stm->params = *output_stream_params;
-
- stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1);
- (*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = 0xbaadbaad;
- assert(stm->instance && "cubeb: EOM");
-
- /* gingerbread uses old channel layout enum */
- if (audiotrack_version_is_gingerbread(ctx)) {
- channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy : AUDIO_CHANNEL_OUT_MONO_Legacy;
- } else {
- channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS;
- }
-
- ctx->klass.ctor(stm->instance, stm->params.stream_type, stm->params.rate,
- AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0,
- audiotrack_refill, stm, 0, 0);
-
- assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad);
-
- if (ctx->klass.check(stm->instance)) {
- ALOG("stream not initialized properly.");
- audiotrack_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- *stream = stm;
-
- return CUBEB_OK;
-}
-
-void
-audiotrack_stream_destroy(cubeb_stream * stream)
-{
- assert(stream->context);
-
- stream->context->klass.dtor(stream->instance);
-
- free(stream->instance);
- stream->instance = NULL;
- free(stream);
-}
-
-int
-audiotrack_stream_start(cubeb_stream * stream)
-{
- assert(stream->instance);
-
- stream->context->klass.start(stream->instance);
- stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
-
- return CUBEB_OK;
-}
-
-int
-audiotrack_stream_stop(cubeb_stream * stream)
-{
- assert(stream->instance);
-
- stream->context->klass.pause(stream->instance);
- stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
-
- return CUBEB_OK;
-}
-
-int
-audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position)
-{
- uint32_t p;
-
- assert(stream->instance && position);
- stream->context->klass.get_position(stream->instance, &p);
- *position = p;
-
- return CUBEB_OK;
-}
-
-int
-audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
-{
- assert(stream->instance && latency);
-
- /* Android returns the latency in ms, we want it in frames. */
- *latency = stream->context->klass.latency(stream->instance);
- /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */
- *latency = (*latency * stream->params.rate) / 1000;
-
- return 0;
-}
-
-int
-audiotrack_stream_set_volume(cubeb_stream * stream, float volume)
-{
- status_t status;
-
- status = stream->context->klass.set_volume(stream->instance, volume, volume);
-
- if (status) {
- return CUBEB_ERROR;
- }
-
- return CUBEB_OK;
-}
-
-static struct cubeb_ops const audiotrack_ops = {
- .init = audiotrack_init,
- .get_backend_id = audiotrack_get_backend_id,
- .get_max_channel_count = audiotrack_get_max_channel_count,
- .get_min_latency = audiotrack_get_min_latency,
- .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
- .enumerate_devices = NULL,
- .destroy = audiotrack_destroy,
- .stream_init = audiotrack_stream_init,
- .stream_destroy = audiotrack_stream_destroy,
- .stream_start = audiotrack_stream_start,
- .stream_stop = audiotrack_stream_stop,
- .stream_get_position = audiotrack_stream_get_position,
- .stream_get_latency = audiotrack_stream_get_latency,
- .stream_set_volume = audiotrack_stream_set_volume,
- .stream_set_panning = NULL,
- .stream_get_current_device = NULL,
- .stream_device_destroy = NULL,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = NULL
-};
diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp
index 9483c2795e..02cd134697 100644
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -6,236 +6,387 @@
*/
#undef NDEBUG
+#include <AudioUnit/AudioUnit.h>
#include <TargetConditionals.h>
#include <assert.h>
#include <mach/mach_time.h>
#include <pthread.h>
#include <stdlib.h>
-#include <AudioUnit/AudioUnit.h>
#if !TARGET_OS_IPHONE
#include <AvailabilityMacros.h>
#include <CoreAudio/AudioHardware.h>
#include <CoreAudio/HostTime.h>
#include <CoreFoundation/CoreFoundation.h>
#endif
-#include <CoreAudio/CoreAudioTypes.h>
-#include <AudioToolbox/AudioToolbox.h>
-#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
-#include "cubeb_panner.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include <AudioToolbox/AudioToolbox.h>
+#include <CoreAudio/CoreAudioTypes.h>
#if !TARGET_OS_IPHONE
#include "cubeb_osx_run_loop.h"
#endif
#include "cubeb_resampler.h"
#include "cubeb_ring_array.h"
-#include "cubeb_utils.h"
#include <algorithm>
#include <atomic>
+#include <set>
+#include <string>
+#include <sys/time.h>
+#include <vector>
-#if !defined(kCFCoreFoundationVersionNumber10_7)
-/* From CoreFoundation CFBase.h */
-#define kCFCoreFoundationVersionNumber10_7 635.00
-#endif
-
-#if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MIN_REQUIRED < 1060
-#define AudioComponent Component
-#define AudioComponentDescription ComponentDescription
-#define AudioComponentFindNext FindNextComponent
-#define AudioComponentInstanceNew OpenAComponent
-#define AudioComponentInstanceDispose CloseComponent
-#endif
+using namespace std;
#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000
-typedef UInt32 AudioFormatFlags;
+typedef UInt32 AudioFormatFlags;
#endif
-#define CUBEB_STREAM_MAX 8
-
-#define AU_OUT_BUS 0
-#define AU_IN_BUS 1
-
-#define PRINT_ERROR_CODE(str, r) do { \
- LOG("System call failed: %s (rv: %d)", str, r); \
-} while(0)
+#define AU_OUT_BUS 0
+#define AU_IN_BUS 1
const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";
+const char * PRIVATE_AGGREGATE_DEVICE_NAME = "CubebAggregateDevice";
+
+#ifdef ALOGV
+#undef ALOGV
+#endif
+#define ALOGV(msg, ...) \
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \
+ ^{ \
+ LOGV(msg, ##__VA_ARGS__); \
+ })
+
+#ifdef ALOG
+#undef ALOG
+#endif
+#define ALOG(msg, ...) \
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \
+ ^{ \
+ LOG(msg, ##__VA_ARGS__); \
+ })
/* Testing empirically, some headsets report a minimal latency that is very
* low, but this does not work in practice. Lie and say the minimum is 256
* frames. */
-const uint32_t SAFE_MIN_LATENCY_FRAMES = 256;
+const uint32_t SAFE_MIN_LATENCY_FRAMES = 128;
const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
-void audiounit_stream_stop_internal(cubeb_stream * stm);
-void audiounit_stream_start_internal(cubeb_stream * stm);
-static void audiounit_close_stream(cubeb_stream *stm);
-static int audiounit_setup_stream(cubeb_stream *stm);
+const AudioObjectPropertyAddress DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress DEVICE_IS_ALIVE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress DEVICES_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress INPUT_DATA_SOURCE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster};
+
+typedef uint32_t device_flags_value;
+
+enum device_flags {
+ DEV_UNKNOWN = 0x00, /* Unknown */
+ DEV_INPUT = 0x01, /* Record device like mic */
+ DEV_OUTPUT = 0x02, /* Playback device like speakers */
+ DEV_SYSTEM_DEFAULT = 0x04, /* System default device */
+ DEV_SELECTED_DEFAULT =
+ 0x08, /* User selected to use the system default device */
+};
+
+void
+audiounit_stream_stop_internal(cubeb_stream * stm);
+static int
+audiounit_stream_start_internal(cubeb_stream * stm);
+static void
+audiounit_close_stream(cubeb_stream * stm);
+static int
+audiounit_setup_stream(cubeb_stream * stm);
+static vector<AudioObjectID>
+audiounit_get_devices_of_type(cubeb_device_type devtype);
+static UInt32
+audiounit_get_device_presentation_latency(AudioObjectID devid,
+ AudioObjectPropertyScope scope);
+
+#if !TARGET_OS_IPHONE
+static AudioObjectID
+audiounit_get_default_device_id(cubeb_device_type type);
+static int
+audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
+static int
+audiounit_uninstall_system_changed_callback(cubeb_stream * stm);
+static void
+audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags);
+#endif
extern cubeb_ops const audiounit_ops;
struct cubeb {
- cubeb_ops const * ops;
+ cubeb_ops const * ops = &audiounit_ops;
owned_critical_section mutex;
- std::atomic<int> active_streams;
+ int active_streams = 0;
uint32_t global_latency_frames = 0;
- int limit_streams;
- cubeb_device_collection_changed_callback collection_changed_callback;
- void * collection_changed_user_ptr;
- /* Differentiate input from output devices. */
- cubeb_device_type collection_changed_devtype;
- uint32_t devtype_device_count;
- AudioObjectID * devtype_device_array;
- // The queue is asynchronously deallocated once all references to it are released
- dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
+ cubeb_device_collection_changed_callback input_collection_changed_callback =
+ nullptr;
+ void * input_collection_changed_user_ptr = nullptr;
+ cubeb_device_collection_changed_callback output_collection_changed_callback =
+ nullptr;
+ void * output_collection_changed_user_ptr = nullptr;
+ // Store list of devices to detect changes
+ vector<AudioObjectID> input_device_array;
+ vector<AudioObjectID> output_device_array;
+ // The queue should be released when it’s no longer needed.
+ dispatch_queue_t serial_queue =
+ dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
+ // Current used channel layout
+ atomic<cubeb_channel_layout> layout{CUBEB_LAYOUT_UNDEFINED};
+ uint32_t channels = 0;
};
-class auto_array_wrapper
+static unique_ptr<AudioChannelLayout, decltype(&free)>
+make_sized_audio_channel_layout(size_t sz)
{
-public:
- explicit auto_array_wrapper(auto_array<float> * ar)
- : float_ar(ar)
- , short_ar(nullptr)
- {assert((float_ar && !short_ar) || (!float_ar && short_ar));}
-
- explicit auto_array_wrapper(auto_array<short> * ar)
- : float_ar(nullptr)
- , short_ar(ar)
- {assert((float_ar && !short_ar) || (!float_ar && short_ar));}
-
- ~auto_array_wrapper() {
- auto_lock l(lock);
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- delete float_ar;
- delete short_ar;
- }
-
- void push(void * elements, size_t length){
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar)
- return float_ar->push(static_cast<float*>(elements), length);
- return short_ar->push(static_cast<short*>(elements), length);
- }
+ assert(sz >= sizeof(AudioChannelLayout));
+ AudioChannelLayout * acl =
+ reinterpret_cast<AudioChannelLayout *>(calloc(1, sz));
+ assert(acl); // Assert the allocation works.
+ return unique_ptr<AudioChannelLayout, decltype(&free)>(acl, free);
+}
- size_t length() {
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar)
- return float_ar->length();
- return short_ar->length();
- }
+enum class io_side {
+ INPUT,
+ OUTPUT,
+};
- void push_silence(size_t length) {
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar)
- return float_ar->push_silence(length);
- return short_ar->push_silence(length);
+static char const *
+to_string(io_side side)
+{
+ switch (side) {
+ case io_side::INPUT:
+ return "input";
+ case io_side::OUTPUT:
+ return "output";
}
+}
- bool pop(void * elements, size_t length) {
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar)
- return float_ar->pop(static_cast<float*>(elements), length);
- return short_ar->pop(static_cast<short*>(elements), length);
- }
+struct device_info {
+ AudioDeviceID id = kAudioObjectUnknown;
+ device_flags_value flags = DEV_UNKNOWN;
+};
- void * data() {
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar)
- return float_ar->data();
- return short_ar->data();
- }
+struct property_listener {
+ AudioDeviceID device_id;
+ const AudioObjectPropertyAddress * property_address;
+ AudioObjectPropertyListenerProc callback;
+ cubeb_stream * stream;
- void clear() {
- assert((float_ar && !short_ar) || (!float_ar && short_ar));
- auto_lock l(lock);
- if (float_ar) {
- float_ar->clear();
- } else {
- short_ar->clear();
- }
+ property_listener(AudioDeviceID id,
+ const AudioObjectPropertyAddress * address,
+ AudioObjectPropertyListenerProc proc, cubeb_stream * stm)
+ : device_id(id), property_address(address), callback(proc), stream(stm)
+ {
}
-
-private:
- auto_array<float> * float_ar;
- auto_array<short> * short_ar;
- owned_critical_section lock;
};
struct cubeb_stream {
+ explicit cubeb_stream(cubeb * context);
+
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
- cubeb_data_callback data_callback;
- cubeb_state_callback state_callback;
- cubeb_device_changed_callback device_changed_callback;
+ void * user_ptr = nullptr;
+ /**/
+
+ cubeb_data_callback data_callback = nullptr;
+ cubeb_state_callback state_callback = nullptr;
+ cubeb_device_changed_callback device_changed_callback = nullptr;
+ owned_critical_section device_changed_callback_lock;
/* Stream creation parameters */
- cubeb_stream_params input_stream_params;
- cubeb_stream_params output_stream_params;
- cubeb_devid input_device;
- bool is_default_input;
- cubeb_devid output_device;
- /* User pointer of data_callback */
- void * user_ptr;
+ cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ device_info input_device;
+ device_info output_device;
/* Format descriptions */
AudioStreamBasicDescription input_desc;
AudioStreamBasicDescription output_desc;
/* I/O AudioUnits */
- AudioUnit input_unit;
- AudioUnit output_unit;
+ AudioUnit input_unit = nullptr;
+ AudioUnit output_unit = nullptr;
/* I/O device sample rate */
- Float64 input_hw_rate;
- Float64 output_hw_rate;
+ Float64 input_hw_rate = 0;
+ Float64 output_hw_rate = 0;
/* Expected I/O thread interleave,
* calculated from I/O hw rate. */
- int expected_output_callbacks_in_a_row;
+ int expected_output_callbacks_in_a_row = 0;
owned_critical_section mutex;
- /* Hold the input samples in every
- * input callback iteration */
- auto_array_wrapper * input_linear_buffer;
- /* Frames on input buffer */
- std::atomic<uint32_t> input_buffer_frames;
+ // Hold the input samples in every input callback iteration.
+ // Only accessed on input/output callback thread and during initial configure.
+ unique_ptr<auto_array_wrapper> input_linear_buffer;
/* Frame counters */
- uint64_t frames_played;
- uint64_t frames_queued;
- std::atomic<int64_t> frames_read;
- std::atomic<bool> shutdown;
- std::atomic<bool> draining;
+ atomic<uint64_t> frames_played{0};
+ uint64_t frames_queued = 0;
+ // How many frames got read from the input since the stream started (includes
+ // padded silence)
+ atomic<int64_t> frames_read{0};
+ // How many frames got written to the output device since the stream started
+ atomic<int64_t> frames_written{0};
+ atomic<bool> shutdown{true};
+ atomic<bool> draining{false};
+ atomic<bool> reinit_pending{false};
+ atomic<bool> destroy_pending{false};
/* Latency requested by the user. */
- uint32_t latency_frames;
- std::atomic<uint64_t> current_latency_frames;
- uint64_t hw_latency_frames;
- std::atomic<float> panning;
- cubeb_resampler * resampler;
- /* This is the number of output callback we got in a row. This is usually one,
- * but can be two when the input and output rate are different, and more when
- * a device has been plugged or unplugged, as there can be some time before
- * the device is ready. */
- std::atomic<int> output_callback_in_a_row;
+ uint32_t latency_frames = 0;
+ atomic<uint32_t> current_latency_frames{0};
+ atomic<uint32_t> total_output_latency_frames{0};
+ unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;
/* This is true if a device change callback is currently running. */
- std::atomic<bool> switching_device;
- std::atomic<bool> buffer_size_change_state{ false };
+ atomic<bool> switching_device{false};
+ atomic<bool> buffer_size_change_state{false};
+ AudioDeviceID aggregate_device_id =
+ kAudioObjectUnknown; // the aggregate device id
+ AudioObjectID plugin_id =
+ kAudioObjectUnknown; // used to create aggregate device
+ /* Mixer interface */
+ unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> mixer;
+ /* Buffer where remixing/resampling will occur when upmixing is required */
+ /* Only accessed from callback thread */
+ unique_ptr<uint8_t[]> temp_buffer;
+ size_t temp_buffer_size = 0; // size in bytes.
+ /* Listeners indicating what system events are monitored. */
+ unique_ptr<property_listener> default_input_listener;
+ unique_ptr<property_listener> default_output_listener;
+ unique_ptr<property_listener> input_alive_listener;
+ unique_ptr<property_listener> input_source_listener;
+ unique_ptr<property_listener> output_source_listener;
};
-bool has_input(cubeb_stream * stm)
+bool
+has_input(cubeb_stream * stm)
{
return stm->input_stream_params.rate != 0;
}
-bool has_output(cubeb_stream * stm)
+bool
+has_output(cubeb_stream * stm)
{
return stm->output_stream_params.rate != 0;
}
+cubeb_channel
+channel_label_to_cubeb_channel(UInt32 label)
+{
+ switch (label) {
+ case kAudioChannelLabel_Left:
+ return CHANNEL_FRONT_LEFT;
+ case kAudioChannelLabel_Right:
+ return CHANNEL_FRONT_RIGHT;
+ case kAudioChannelLabel_Center:
+ return CHANNEL_FRONT_CENTER;
+ case kAudioChannelLabel_LFEScreen:
+ return CHANNEL_LOW_FREQUENCY;
+ case kAudioChannelLabel_LeftSurround:
+ return CHANNEL_BACK_LEFT;
+ case kAudioChannelLabel_RightSurround:
+ return CHANNEL_BACK_RIGHT;
+ case kAudioChannelLabel_LeftCenter:
+ return CHANNEL_FRONT_LEFT_OF_CENTER;
+ case kAudioChannelLabel_RightCenter:
+ return CHANNEL_FRONT_RIGHT_OF_CENTER;
+ case kAudioChannelLabel_CenterSurround:
+ return CHANNEL_BACK_CENTER;
+ case kAudioChannelLabel_LeftSurroundDirect:
+ return CHANNEL_SIDE_LEFT;
+ case kAudioChannelLabel_RightSurroundDirect:
+ return CHANNEL_SIDE_RIGHT;
+ case kAudioChannelLabel_TopCenterSurround:
+ return CHANNEL_TOP_CENTER;
+ case kAudioChannelLabel_VerticalHeightLeft:
+ return CHANNEL_TOP_FRONT_LEFT;
+ case kAudioChannelLabel_VerticalHeightCenter:
+ return CHANNEL_TOP_FRONT_CENTER;
+ case kAudioChannelLabel_VerticalHeightRight:
+ return CHANNEL_TOP_FRONT_RIGHT;
+ case kAudioChannelLabel_TopBackLeft:
+ return CHANNEL_TOP_BACK_LEFT;
+ case kAudioChannelLabel_TopBackCenter:
+ return CHANNEL_TOP_BACK_CENTER;
+ case kAudioChannelLabel_TopBackRight:
+ return CHANNEL_TOP_BACK_RIGHT;
+ default:
+ return CHANNEL_UNKNOWN;
+ }
+}
+
+AudioChannelLabel
+cubeb_channel_to_channel_label(cubeb_channel channel)
+{
+ switch (channel) {
+ case CHANNEL_FRONT_LEFT:
+ return kAudioChannelLabel_Left;
+ case CHANNEL_FRONT_RIGHT:
+ return kAudioChannelLabel_Right;
+ case CHANNEL_FRONT_CENTER:
+ return kAudioChannelLabel_Center;
+ case CHANNEL_LOW_FREQUENCY:
+ return kAudioChannelLabel_LFEScreen;
+ case CHANNEL_BACK_LEFT:
+ return kAudioChannelLabel_LeftSurround;
+ case CHANNEL_BACK_RIGHT:
+ return kAudioChannelLabel_RightSurround;
+ case CHANNEL_FRONT_LEFT_OF_CENTER:
+ return kAudioChannelLabel_LeftCenter;
+ case CHANNEL_FRONT_RIGHT_OF_CENTER:
+ return kAudioChannelLabel_RightCenter;
+ case CHANNEL_BACK_CENTER:
+ return kAudioChannelLabel_CenterSurround;
+ case CHANNEL_SIDE_LEFT:
+ return kAudioChannelLabel_LeftSurroundDirect;
+ case CHANNEL_SIDE_RIGHT:
+ return kAudioChannelLabel_RightSurroundDirect;
+ case CHANNEL_TOP_CENTER:
+ return kAudioChannelLabel_TopCenterSurround;
+ case CHANNEL_TOP_FRONT_LEFT:
+ return kAudioChannelLabel_VerticalHeightLeft;
+ case CHANNEL_TOP_FRONT_CENTER:
+ return kAudioChannelLabel_VerticalHeightCenter;
+ case CHANNEL_TOP_FRONT_RIGHT:
+ return kAudioChannelLabel_VerticalHeightRight;
+ case CHANNEL_TOP_BACK_LEFT:
+ return kAudioChannelLabel_TopBackLeft;
+ case CHANNEL_TOP_BACK_CENTER:
+ return kAudioChannelLabel_TopBackCenter;
+ case CHANNEL_TOP_BACK_RIGHT:
+ return kAudioChannelLabel_TopBackRight;
+ default:
+ return kAudioChannelLabel_Unknown;
+ }
+}
+
#if TARGET_OS_IPHONE
typedef UInt32 AudioDeviceID;
typedef UInt32 AudioObjectID;
#define AudioGetCurrentHostTime mach_absolute_time
+#endif
+
uint64_t
-AudioConvertHostTimeToNanos(uint64_t host_time)
+ConvertHostTimeToNanos(uint64_t host_time)
{
static struct mach_timebase_info timebase_info;
static bool initialized = false;
@@ -251,27 +402,34 @@ AudioConvertHostTimeToNanos(uint64_t host_time)
}
return (uint64_t)answer;
}
-#endif
-static int64_t
-audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream)
+static void
+audiounit_increment_active_streams(cubeb * ctx)
{
- if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) {
- return 0;
- }
+ ctx->mutex.assert_current_thread_owns();
+ ctx->active_streams += 1;
+}
- uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime);
- uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
+static void
+audiounit_decrement_active_streams(cubeb * ctx)
+{
+ ctx->mutex.assert_current_thread_owns();
+ ctx->active_streams -= 1;
+}
- return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL;
+static int
+audiounit_active_streams(cubeb * ctx)
+{
+ ctx->mutex.assert_current_thread_owns();
+ return ctx->active_streams;
}
static void
-audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames)
+audiounit_set_global_latency(cubeb * ctx, uint32_t latency_frames)
{
- stm->mutex.assert_current_thread_owns();
- assert(stm->context->active_streams == 1);
- stm->context->global_latency_frames = latency_frames;
+ ctx->mutex.assert_current_thread_owns();
+ assert(audiounit_active_streams(ctx) == 1);
+ ctx->global_latency_frames = latency_frames;
}
static void
@@ -283,10 +441,8 @@ audiounit_make_silent(AudioBuffer * ioData)
}
static OSStatus
-audiounit_render_input(cubeb_stream * stm,
- AudioUnitRenderActionFlags * flags,
- AudioTimeStamp const * tstamp,
- UInt32 bus,
+audiounit_render_input(cubeb_stream * stm, AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp, UInt32 bus,
UInt32 input_frames)
{
/* Create the AudioBufferList to store input. */
@@ -294,66 +450,76 @@ audiounit_render_input(cubeb_stream * stm,
input_buffer_list.mBuffers[0].mDataByteSize =
stm->input_desc.mBytesPerFrame * input_frames;
input_buffer_list.mBuffers[0].mData = nullptr;
- input_buffer_list.mBuffers[0].mNumberChannels = stm->input_desc.mChannelsPerFrame;
+ input_buffer_list.mBuffers[0].mNumberChannels =
+ stm->input_desc.mChannelsPerFrame;
input_buffer_list.mNumberBuffers = 1;
/* Render input samples */
- OSStatus r = AudioUnitRender(stm->input_unit,
- flags,
- tstamp,
- bus,
- input_frames,
- &input_buffer_list);
+ OSStatus r = AudioUnitRender(stm->input_unit, flags, tstamp, bus,
+ input_frames, &input_buffer_list);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitRender", r);
- return r;
+ LOG("AudioUnitRender rv=%d", r);
+ if (r != kAudioUnitErr_CannotDoInCurrentContext) {
+ return r;
+ }
+ if (stm->output_unit) {
+ // kAudioUnitErr_CannotDoInCurrentContext is returned when using a BT
+ // headset and the profile is changed from A2DP to HFP/HSP. The previous
+ // output device is no longer valid and must be reset.
+ audiounit_reinit_stream_async(stm, DEV_INPUT | DEV_OUTPUT);
+ }
+ // For now state that no error occurred and feed silence, stream will be
+ // resumed once reinit has completed.
+ ALOGV("(%p) input: reinit pending feeding silence instead", stm);
+ stm->input_linear_buffer->push_silence(input_frames *
+ stm->input_desc.mChannelsPerFrame);
+ } else {
+ /* Copy input data in linear buffer. */
+ stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
+ input_frames *
+ stm->input_desc.mChannelsPerFrame);
}
- /* Copy input data in linear buffer. */
- stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
- input_frames * stm->input_desc.mChannelsPerFrame);
-
- LOGV("(%p) input: buffers %d, size %d, channels %d, frames %d.",
- stm, input_buffer_list.mNumberBuffers,
- input_buffer_list.mBuffers[0].mDataByteSize,
- input_buffer_list.mBuffers[0].mNumberChannels,
- input_frames);
-
/* Advance input frame counter. */
assert(input_frames > 0);
stm->frames_read += input_frames;
+ ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, "
+ "total frames %lu.",
+ stm, (unsigned int)input_buffer_list.mNumberBuffers,
+ (unsigned int)input_buffer_list.mBuffers[0].mDataByteSize,
+ (unsigned int)input_buffer_list.mBuffers[0].mNumberChannels,
+ (unsigned int)input_frames,
+ stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame);
+
return noErr;
}
static OSStatus
-audiounit_input_callback(void * user_ptr,
- AudioUnitRenderActionFlags * flags,
- AudioTimeStamp const * tstamp,
- UInt32 bus,
- UInt32 input_frames,
- AudioBufferList * /* bufs */)
+audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp, UInt32 bus,
+ UInt32 input_frames, AudioBufferList * /* bufs */)
{
cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
- long outframes;
assert(stm->input_unit != NULL);
assert(AU_IN_BUS == bus);
if (stm->shutdown) {
- LOG("(%p) input shutdown", stm);
+ ALOG("(%p) input shutdown", stm);
return noErr;
}
- // This happens when we're finally getting a new input callback after having
- // switched device, we can clear the input buffer now, only keeping the data
- // we just got.
- if (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row) {
- stm->input_linear_buffer->pop(
- nullptr,
- stm->input_linear_buffer->length() -
- input_frames * stm->input_stream_params.channels);
+ if (stm->draining) {
+ OSStatus r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ // Only fire state callback in input-only stream. For duplex stream,
+ // the state callback will be fired in output callback.
+ if (stm->output_unit == NULL) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ }
+ return noErr;
}
OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames);
@@ -363,147 +529,199 @@ audiounit_input_callback(void * user_ptr,
// Full Duplex. We'll call data_callback in the AudioUnit output callback.
if (stm->output_unit != NULL) {
- stm->output_callback_in_a_row = 0;
return noErr;
}
/* Input only. Call the user callback through resampler.
Resampler will deliver input buffer in the correct rate. */
- assert(input_frames <= stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame);
- long total_input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
- outframes = cubeb_resampler_fill(stm->resampler,
- stm->input_linear_buffer->data(),
- &total_input_frames,
- NULL,
- 0);
+ assert(input_frames <= stm->input_linear_buffer->length() /
+ stm->input_desc.mChannelsPerFrame);
+ long total_input_frames =
+ stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
+ long outframes = cubeb_resampler_fill(stm->resampler.get(),
+ stm->input_linear_buffer->data(),
+ &total_input_frames, NULL, 0);
+ stm->draining = outframes < total_input_frames;
+
// Reset input buffer
stm->input_linear_buffer->clear();
- if (outframes < 0 || outframes != input_frames) {
- stm->shutdown = true;
- return noErr;
- }
-
return noErr;
}
-static bool
-is_extra_input_needed(cubeb_stream * stm)
+static void
+audiounit_mix_output_buffer(cubeb_stream * stm, size_t output_frames,
+ void * input_buffer, size_t input_buffer_size,
+ void * output_buffer, size_t output_buffer_size)
{
- /* If the output callback came first and this is a duplex stream, we need to
- * fill in some additional silence in the resampler.
- * Otherwise, if we had more than expected callbacks in a row, or we're currently
- * switching, we add some silence as well to compensate for the fact that
- * we're lacking some input data. */
+ assert(input_buffer_size >=
+ cubeb_sample_size(stm->output_stream_params.format) *
+ stm->output_stream_params.channels * output_frames);
+ assert(output_buffer_size >= stm->output_desc.mBytesPerFrame * output_frames);
- /* If resampling is taking place after every output callback
- * the input buffer expected to be empty. Any frame left over
- * from resampling is stored inside the resampler available to
- * be used in next iteration as needed.
- * BUT when noop_resampler is operating we have left over
- * frames since it does not store anything internally. */
- return stm->frames_read == 0 ||
- (stm->input_linear_buffer->length() == 0 &&
- (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row ||
- stm->switching_device));
+ int r = cubeb_mixer_mix(stm->mixer.get(), output_frames, input_buffer,
+ input_buffer_size, output_buffer, output_buffer_size);
+ if (r != 0) {
+ LOG("Remix error = %d", r);
+ }
+}
+
+// Return how many input frames (sampled at input_hw_rate) are needed to provide
+// output_frames (sampled at output_stream_params.rate)
+static int64_t
+minimum_resampling_input_frames(cubeb_stream * stm, uint32_t output_frames)
+{
+ if (stm->input_hw_rate == stm->output_stream_params.rate) {
+ // Fast path.
+ return output_frames;
+ }
+ return ceil(stm->input_hw_rate * output_frames /
+ stm->output_stream_params.rate);
}
static OSStatus
audiounit_output_callback(void * user_ptr,
AudioUnitRenderActionFlags * /* flags */,
- AudioTimeStamp const * tstamp,
- UInt32 bus,
- UInt32 output_frames,
- AudioBufferList * outBufferList)
+ AudioTimeStamp const * tstamp, UInt32 bus,
+ UInt32 output_frames, AudioBufferList * outBufferList)
{
assert(AU_OUT_BUS == bus);
assert(outBufferList->mNumberBuffers == 1);
cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
- stm->output_callback_in_a_row++;
-
- LOGV("(%p) output: buffers %d, size %d, channels %d, frames %d.",
- stm, outBufferList->mNumberBuffers,
- outBufferList->mBuffers[0].mDataByteSize,
- outBufferList->mBuffers[0].mNumberChannels, output_frames);
-
- long outframes = 0, input_frames = 0;
- void * output_buffer = NULL, * input_buffer = NULL;
+ uint64_t now = ConvertHostTimeToNanos(mach_absolute_time());
+ uint64_t audio_output_time = ConvertHostTimeToNanos(tstamp->mHostTime);
+ uint64_t output_latency_ns = audio_output_time - now;
+
+ const int ns2s = 1e9;
+ // The total output latency is the timestamp difference + the stream latency +
+ // the hardware latency.
+ stm->total_output_latency_frames =
+ output_latency_ns * stm->output_hw_rate / ns2s +
+ stm->current_latency_frames;
+
+ ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input "
+ "frames %lu.",
+ stm, (unsigned int)outBufferList->mNumberBuffers,
+ (unsigned int)outBufferList->mBuffers[0].mDataByteSize,
+ (unsigned int)outBufferList->mBuffers[0].mNumberChannels,
+ (unsigned int)output_frames,
+ has_input(stm) ? stm->input_linear_buffer->length() /
+ stm->input_desc.mChannelsPerFrame
+ : 0);
+
+ long input_frames = 0;
+ void *output_buffer = NULL, *input_buffer = NULL;
if (stm->shutdown) {
- LOG("(%p) output shutdown.", stm);
+ ALOG("(%p) output shutdown.", stm);
audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
- stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm);
if (stm->draining) {
OSStatus r = AudioOutputUnitStop(stm->output_unit);
assert(r == 0);
- if (stm->input_unit) {
- r = AudioOutputUnitStop(stm->input_unit);
- assert(r == 0);
- }
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
+
/* Get output buffer. */
- output_buffer = outBufferList->mBuffers[0].mData;
+ if (stm->mixer) {
+ // If remixing needs to occur, we can't directly work in our final
+ // destination buffer as data may be overwritten or too small to start with.
+ size_t size_needed = output_frames * stm->output_stream_params.channels *
+ cubeb_sample_size(stm->output_stream_params.format);
+ if (stm->temp_buffer_size < size_needed) {
+ stm->temp_buffer.reset(new uint8_t[size_needed]);
+ stm->temp_buffer_size = size_needed;
+ }
+ output_buffer = stm->temp_buffer.get();
+ } else {
+ output_buffer = outBufferList->mBuffers[0].mData;
+ }
+
+ stm->frames_written += output_frames;
+
/* If Full duplex get also input buffer */
if (stm->input_unit != NULL) {
- if (is_extra_input_needed(stm)) {
- uint32_t min_input_frames_required = ceilf(stm->input_hw_rate / stm->output_hw_rate *
- stm->input_buffer_frames);
- stm->input_linear_buffer->push_silence(min_input_frames_required * stm->input_desc.mChannelsPerFrame);
- LOG("(%p) %s pushed %u frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," :
- stm->switching_device ? "Device switching," : "Drop out,", min_input_frames_required);
- }
- // The input buffer
+ /* If the output callback came first and this is a duplex stream, we need to
+ * fill in some additional silence in the resampler.
+ * Otherwise, if we had more than expected callbacks in a row, or we're
+ * currently switching, we add some silence as well to compensate for the
+ * fact that we're lacking some input data. */
+ uint32_t input_frames_needed =
+ minimum_resampling_input_frames(stm, stm->frames_written);
+ long missing_frames = input_frames_needed - stm->frames_read;
+ if (missing_frames > 0) {
+ stm->input_linear_buffer->push_silence(missing_frames *
+ stm->input_desc.mChannelsPerFrame);
+ stm->frames_read = input_frames_needed;
+
+ ALOG("(%p) %s pushed %ld frames of input silence.", stm,
+ stm->frames_read == 0 ? "Input hasn't started,"
+ : stm->switching_device ? "Device switching,"
+ : "Drop out,",
+ missing_frames);
+ }
input_buffer = stm->input_linear_buffer->data();
- // Number of input frames in the buffer
- input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
+ // Number of input frames in the buffer. It will change to actually used
+ // frames inside fill
+ input_frames =
+ stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
}
/* Call user callback through resampler. */
- outframes = cubeb_resampler_fill(stm->resampler,
- input_buffer,
- input_buffer ? &input_frames : NULL,
- output_buffer,
- output_frames);
+ long outframes = cubeb_resampler_fill(stm->resampler.get(), input_buffer,
+ input_buffer ? &input_frames : NULL,
+ output_buffer, output_frames);
if (input_buffer) {
- stm->input_linear_buffer->pop(nullptr, input_frames * stm->input_desc.mChannelsPerFrame);
+ // Pop from the buffer the frames used by the the resampler.
+ stm->input_linear_buffer->pop(input_frames *
+ stm->input_desc.mChannelsPerFrame);
}
- if (outframes < 0) {
+ if (outframes < 0 || outframes > output_frames) {
stm->shutdown = true;
+ OSStatus r = AudioOutputUnitStop(stm->output_unit);
+ assert(r == 0);
+ if (stm->input_unit) {
+ r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ }
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
}
- size_t outbpf = stm->output_desc.mBytesPerFrame;
- stm->draining = outframes < output_frames;
+ stm->draining = (UInt32)outframes < output_frames;
stm->frames_played = stm->frames_queued;
stm->frames_queued += outframes;
- AudioFormatFlags outaff = stm->output_desc.mFormatFlags;
- float panning = (stm->output_desc.mChannelsPerFrame == 2) ?
- stm->panning.load(std::memory_order_relaxed) : 0.0f;
-
/* Post process output samples. */
if (stm->draining) {
/* Clear missing frames (silence) */
- memset((uint8_t*)output_buffer + outframes * outbpf, 0, (output_frames - outframes) * outbpf);
+ size_t channels = stm->output_stream_params.channels;
+ size_t missing_samples = (output_frames - outframes) * channels;
+ size_t size_sample = cubeb_sample_size(stm->output_stream_params.format);
+ /* number of bytes that have been filled with valid audio by the callback.
+ */
+ size_t audio_byte_count = outframes * channels * size_sample;
+ PodZero((uint8_t *)output_buffer + audio_byte_count,
+ missing_samples * size_sample);
}
- /* Pan stereo. */
- if (panning != 0.0f) {
- if (outaff & kAudioFormatFlagIsFloat) {
- cubeb_pan_stereo_buffer_float((float*)output_buffer, outframes, panning);
- } else if (outaff & kAudioFormatFlagIsSignedInteger) {
- cubeb_pan_stereo_buffer_int((short*)output_buffer, outframes, panning);
- }
+
+ /* Mixing */
+ if (stm->mixer) {
+ audiounit_mix_output_buffer(stm, output_frames, output_buffer,
+ stm->temp_buffer_size,
+ outBufferList->mBuffers[0].mData,
+ outBufferList->mBuffers[0].mDataByteSize);
}
+
return noErr;
}
@@ -511,25 +729,11 @@ extern "C" {
int
audiounit_init(cubeb ** context, char const * /* context_name */)
{
- cubeb * ctx;
-
- *context = NULL;
-
- ctx = (cubeb *)calloc(1, sizeof(cubeb));
- assert(ctx);
- // Placement new to call the ctors of cubeb members.
- new (ctx) cubeb();
-
- ctx->ops = &audiounit_ops;
-
- ctx->active_streams = 0;
-
- ctx->limit_streams = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber10_7;
#if !TARGET_OS_IPHONE
cubeb_set_coreaudio_notification_runloop();
#endif
- *context = ctx;
+ *context = new cubeb;
return CUBEB_OK;
}
@@ -542,255 +746,338 @@ audiounit_get_backend_id(cubeb * /* ctx */)
}
#if !TARGET_OS_IPHONE
+
+static int
+audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
static int
-audiounit_get_output_device_id(AudioDeviceID * device_id)
+audiounit_stream_set_volume(cubeb_stream * stm, float volume);
+
+static int
+audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)
{
- UInt32 size;
- OSStatus r;
- AudioObjectPropertyAddress output_device_address = {
- kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
+ assert(stm);
- size = sizeof(*device_id);
+ device_info * info = nullptr;
+ cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
- r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &output_device_address,
- 0,
- NULL,
- &size,
- device_id);
- if (r != noErr) {
- PRINT_ERROR_CODE("output_device_id", r);
- return CUBEB_ERROR;
+ if (side == io_side::INPUT) {
+ info = &stm->input_device;
+ type = CUBEB_DEVICE_TYPE_INPUT;
+ } else if (side == io_side::OUTPUT) {
+ info = &stm->output_device;
+ type = CUBEB_DEVICE_TYPE_OUTPUT;
}
+ memset(info, 0, sizeof(device_info));
+ info->id = id;
- return CUBEB_OK;
-}
-
-static int
-audiounit_get_input_device_id(AudioDeviceID * device_id)
-{
- UInt32 size;
- OSStatus r;
- AudioObjectPropertyAddress input_device_address = {
- kAudioHardwarePropertyDefaultInputDevice,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
-
- size = sizeof(*device_id);
+ if (side == io_side::INPUT) {
+ info->flags |= DEV_INPUT;
+ } else if (side == io_side::OUTPUT) {
+ info->flags |= DEV_OUTPUT;
+ }
- r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
- &input_device_address,
- 0,
- NULL,
- &size,
- device_id);
- if (r != noErr) {
+ AudioDeviceID default_device_id = audiounit_get_default_device_id(type);
+ if (default_device_id == kAudioObjectUnknown) {
return CUBEB_ERROR;
}
+ if (id == kAudioObjectUnknown) {
+ info->id = default_device_id;
+ info->flags |= DEV_SELECTED_DEFAULT;
+ }
+
+ if (info->id == default_device_id) {
+ info->flags |= DEV_SYSTEM_DEFAULT;
+ }
+
+ assert(info->id);
+ assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) ||
+ !(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT);
return CUBEB_OK;
}
-static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
-static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
-static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
-
static int
-audiounit_reinit_stream(cubeb_stream * stm)
+audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags)
{
auto_lock context_lock(stm->context->mutex);
+ assert((flags & DEV_INPUT && stm->input_unit) ||
+ (flags & DEV_OUTPUT && stm->output_unit));
if (!stm->shutdown) {
audiounit_stream_stop_internal(stm);
}
int r = audiounit_uninstall_device_changed_callback(stm);
if (r != CUBEB_OK) {
- LOG("(%p) Could not uninstall the device changed callback", stm);
+ LOG("(%p) Could not uninstall all device change listeners.", stm);
}
{
auto_lock lock(stm->mutex);
float volume = 0.0;
- int vol_rv = audiounit_stream_get_volume(stm, &volume);
+ int vol_rv = CUBEB_ERROR;
+ if (stm->output_unit) {
+ vol_rv = audiounit_stream_get_volume(stm, &volume);
+ }
audiounit_close_stream(stm);
+ /* Reinit occurs in one of the following case:
+ * - When the device is not alive any more
+ * - When the default system device change.
+ * - The bluetooth device changed from A2DP to/from HFP/HSP profile
+ * We first attempt to re-use the same device id, should that fail we will
+ * default to the (potentially new) default device. */
+ AudioDeviceID input_device =
+ flags & DEV_INPUT ? stm->input_device.id : kAudioObjectUnknown;
+ if (flags & DEV_INPUT) {
+ r = audiounit_set_device_info(stm, input_device, io_side::INPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Set input device info failed. This can happen when last "
+ "media device is unplugged",
+ stm);
+ return CUBEB_ERROR;
+ }
+ }
+
+ /* Always use the default output on reinit. This is not correct in every
+ * case but it is sufficient for Firefox and prevent reinit from reporting
+ * failures. It will change soon when reinit mechanism will be updated. */
+ r = audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::OUTPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Set output device info failed. This can happen when last media "
+ "device is unplugged",
+ stm);
+ return CUBEB_ERROR;
+ }
+
if (audiounit_setup_stream(stm) != CUBEB_OK) {
LOG("(%p) Stream reinit failed.", stm);
- return CUBEB_ERROR;
+ if (flags & DEV_INPUT && input_device != kAudioObjectUnknown) {
+ // Attempt to re-use the same device-id failed, so attempt again with
+ // default input device.
+ audiounit_close_stream(stm);
+ if (audiounit_set_device_info(stm, kAudioObjectUnknown,
+ io_side::INPUT) != CUBEB_OK ||
+ audiounit_setup_stream(stm) != CUBEB_OK) {
+ LOG("(%p) Second stream reinit failed.", stm);
+ return CUBEB_ERROR;
+ }
+ }
}
if (vol_rv == CUBEB_OK) {
audiounit_stream_set_volume(stm, volume);
}
- // Reset input frames to force new stream pre-buffer
- // silence if needed, check `is_extra_input_needed()`
- stm->frames_read = 0;
-
// If the stream was running, start it again.
if (!stm->shutdown) {
- audiounit_stream_start_internal(stm);
+ r = audiounit_stream_start_internal(stm);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
}
}
return CUBEB_OK;
}
-static OSStatus
-audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
- const AudioObjectPropertyAddress * addresses,
- void * user)
+static void
+audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags)
{
- cubeb_stream * stm = (cubeb_stream*) user;
+ if (std::atomic_exchange(&stm->reinit_pending, true)) {
+ // A reinit task is already pending, nothing more to do.
+ ALOG("(%p) re-init stream task already pending, cancelling request", stm);
+ return;
+ }
+
+ // Use a new thread, through the queue, to avoid deadlock when calling
+ // Get/SetProperties method from inside notify callback
+ dispatch_async(stm->context->serial_queue, ^() {
+ if (stm->destroy_pending) {
+ ALOG("(%p) stream pending destroy, cancelling reinit task", stm);
+ return;
+ }
+
+ if (audiounit_reinit_stream(stm, flags) != CUBEB_OK) {
+ if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) {
+ LOG("(%p) Could not uninstall system changed callback", stm);
+ }
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ LOG("(%p) Could not reopen the stream after switching.", stm);
+ }
+ stm->switching_device = false;
+ stm->reinit_pending = false;
+ });
+}
+
+static char const *
+event_addr_to_string(AudioObjectPropertySelector selector)
+{
+ switch (selector) {
+ case kAudioHardwarePropertyDefaultOutputDevice:
+ return "kAudioHardwarePropertyDefaultOutputDevice";
+ case kAudioHardwarePropertyDefaultInputDevice:
+ return "kAudioHardwarePropertyDefaultInputDevice";
+ case kAudioDevicePropertyDeviceIsAlive:
+ return "kAudioDevicePropertyDeviceIsAlive";
+ case kAudioDevicePropertyDataSource:
+ return "kAudioDevicePropertyDataSource";
+ default:
+ return "Unknown";
+ }
+}
+
+static OSStatus
+audiounit_property_listener_callback(
+ AudioObjectID id, UInt32 address_count,
+ const AudioObjectPropertyAddress * addresses, void * user)
+{
+ cubeb_stream * stm = (cubeb_stream *)user;
+ if (stm->switching_device) {
+ LOG("Switching is already taking place. Skip Event %s for id=%d",
+ event_addr_to_string(addresses[0].mSelector), id);
+ return noErr;
+ }
stm->switching_device = true;
- LOG("(%p) Audio device changed, %d events.", stm, address_count);
+ LOG("(%p) Audio device changed, %u events.", stm,
+ (unsigned int)address_count);
for (UInt32 i = 0; i < address_count; i++) {
- switch(addresses[i].mSelector) {
- case kAudioHardwarePropertyDefaultOutputDevice: {
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i);
- // Allow restart to choose the new default
- stm->output_device = nullptr;
- }
- break;
- case kAudioHardwarePropertyDefaultInputDevice: {
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultInputDevice", i);
- // Allow restart to choose the new default
- stm->input_device = nullptr;
- }
- break;
- case kAudioDevicePropertyDeviceIsAlive: {
- LOG("Event[%d] - mSelector == kAudioDevicePropertyDeviceIsAlive", i);
- // If this is the default input device ignore the event,
- // kAudioHardwarePropertyDefaultInputDevice will take care of the switch
- if (stm->is_default_input) {
- LOG("It's the default input device, ignore the event");
- return noErr;
- }
- // Allow restart to choose the new default. Event register only for input.
- stm->input_device = nullptr;
- }
- break;
- case kAudioDevicePropertyDataSource: {
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i);
- return noErr;
- }
+ switch (addresses[i].mSelector) {
+ case kAudioHardwarePropertyDefaultOutputDevice: {
+ LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice "
+ "for id=%d",
+ (unsigned int)i, id);
+ } break;
+ case kAudioHardwarePropertyDefaultInputDevice: {
+ LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice "
+ "for id=%d",
+ (unsigned int)i, id);
+ } break;
+ case kAudioDevicePropertyDeviceIsAlive: {
+ LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for "
+ "id=%d",
+ (unsigned int)i, id);
+ // If this is the default input device ignore the event,
+ // kAudioHardwarePropertyDefaultInputDevice will take care of the switch
+ if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) {
+ LOG("It's the default input device, ignore the event");
+ stm->switching_device = false;
+ return noErr;
+ }
+ } break;
+ case kAudioDevicePropertyDataSource: {
+ LOG("Event[%u] - mSelector == kAudioDevicePropertyDataSource for id=%d",
+ (unsigned int)i, id);
+ } break;
+ default:
+ LOG("Event[%u] - mSelector == Unexpected Event id %d, return",
+ (unsigned int)i, addresses[i].mSelector);
+ stm->switching_device = false;
+ return noErr;
}
}
+ // Allow restart to choose the new default
+ device_flags_value switch_side = DEV_UNKNOWN;
+ if (has_input(stm)) {
+ switch_side |= DEV_INPUT;
+ }
+ if (has_output(stm)) {
+ switch_side |= DEV_OUTPUT;
+ }
+
for (UInt32 i = 0; i < address_count; i++) {
- switch(addresses[i].mSelector) {
+ switch (addresses[i].mSelector) {
case kAudioHardwarePropertyDefaultOutputDevice:
case kAudioHardwarePropertyDefaultInputDevice:
case kAudioDevicePropertyDeviceIsAlive:
/* fall through */
case kAudioDevicePropertyDataSource: {
- auto_lock lock(stm->mutex);
- if (stm->device_changed_callback) {
- stm->device_changed_callback(stm->user_ptr);
- }
- break;
+ auto_lock dev_cb_lock(stm->device_changed_callback_lock);
+ if (stm->device_changed_callback) {
+ stm->device_changed_callback(stm->user_ptr);
}
+ break;
+ }
}
}
- // Use a new thread, through the queue, to avoid deadlock when calling
- // Get/SetProperties method from inside notify callback
- dispatch_async(stm->context->serial_queue, ^() {
- if (audiounit_reinit_stream(stm) != CUBEB_OK) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
- LOG("(%p) Could not reopen the stream after switching.", stm);
- }
- stm->switching_device = false;
- });
+ audiounit_reinit_stream_async(stm, switch_side);
return noErr;
}
OSStatus
-audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector,
- AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener)
+audiounit_add_listener(const property_listener * listener)
{
- AudioObjectPropertyAddress address = {
- selector,
- scope,
- kAudioObjectPropertyElementMaster
- };
-
- return AudioObjectAddPropertyListener(id, &address, listener, stm);
+ assert(listener);
+ return AudioObjectAddPropertyListener(listener->device_id,
+ listener->property_address,
+ listener->callback, listener->stream);
}
OSStatus
-audiounit_remove_listener(cubeb_stream * stm, AudioDeviceID id,
- AudioObjectPropertySelector selector,
- AudioObjectPropertyScope scope,
- AudioObjectPropertyListenerProc listener)
+audiounit_remove_listener(const property_listener * listener)
{
- AudioObjectPropertyAddress address = {
- selector,
- scope,
- kAudioObjectPropertyElementMaster
- };
-
- return AudioObjectRemovePropertyListener(id, &address, listener, stm);
+ assert(listener);
+ return AudioObjectRemovePropertyListener(
+ listener->device_id, listener->property_address, listener->callback,
+ listener->stream);
}
-static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type);
-
static int
audiounit_install_device_changed_callback(cubeb_stream * stm)
{
- OSStatus r;
+ OSStatus rv;
+ int r = CUBEB_OK;
if (stm->output_unit) {
- /* This event will notify us when the data source on the same device changes,
- * for example when the user plugs in a normal (non-usb) headset in the
- * headphone jack. */
- AudioDeviceID output_dev_id;
- r = audiounit_get_output_device_id(&output_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource", r);
- return CUBEB_ERROR;
+ /* This event will notify us when the data source on the same device
+ * changes, for example when the user plugs in a normal (non-usb) headset in
+ * the headphone jack. */
+ stm->output_source_listener.reset(new property_listener(
+ stm->output_device.id, &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->output_source_listener.get());
+ if (rv != noErr) {
+ stm->output_source_listener.reset();
+ LOG("AudioObjectAddPropertyListener/output/"
+ "kAudioDevicePropertyDataSource rv=%d, device id=%d",
+ rv, stm->output_device.id);
+ r = CUBEB_ERROR;
}
}
if (stm->input_unit) {
- /* This event will notify us when the data source on the input device changes. */
- AudioDeviceID input_dev_id;
- r = audiounit_get_input_device_id(&input_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource", r);
- return CUBEB_ERROR;
+ /* This event will notify us when the data source on the input device
+ * changes. */
+ stm->input_source_listener.reset(new property_listener(
+ stm->input_device.id, &INPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->input_source_listener.get());
+ if (rv != noErr) {
+ stm->input_source_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource "
+ "rv=%d, device id=%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
}
/* Event to notify when the input is going away. */
- AudioDeviceID dev = stm->input_device ? reinterpret_cast<intptr_t>(stm->input_device) :
- audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
- r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r);
- return CUBEB_ERROR;
+ stm->input_alive_listener.reset(new property_listener(
+ stm->input_device.id, &DEVICE_IS_ALIVE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->input_alive_listener.get());
+ if (rv != noErr) {
+ stm->input_alive_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/"
+ "kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
}
}
- return CUBEB_OK;
+ return r;
}
static int
@@ -800,23 +1087,33 @@ audiounit_install_system_changed_callback(cubeb_stream * stm)
if (stm->output_unit) {
/* This event will notify us when the default audio device changes,
- * for example when the user plugs in a USB headset and the system chooses it
- * automatically as the default, or when another device is chosen in the
+ * for example when the user plugs in a USB headset and the system chooses
+ * it automatically as the default, or when another device is chosen in the
* dropdown list. */
- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
+ stm->default_output_listener.reset(new property_listener(
+ kAudioObjectSystemObject, &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ r = audiounit_add_listener(stm->default_output_listener.get());
if (r != noErr) {
- LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r);
+ stm->default_output_listener.reset();
+ LOG("AudioObjectAddPropertyListener/output/"
+ "kAudioHardwarePropertyDefaultOutputDevice rv=%d",
+ r);
return CUBEB_ERROR;
}
}
if (stm->input_unit) {
/* This event will notify us when the default input device changes. */
- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
+ stm->default_input_listener.reset(new property_listener(
+ kAudioObjectSystemObject, &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ r = audiounit_add_listener(stm->default_input_listener.get());
if (r != noErr) {
- LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r);
+ stm->default_input_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/"
+ "kAudioHardwarePropertyDefaultInputDevice rv=%d",
+ r);
return CUBEB_ERROR;
}
}
@@ -827,36 +1124,44 @@ audiounit_install_system_changed_callback(cubeb_stream * stm)
static int
audiounit_uninstall_device_changed_callback(cubeb_stream * stm)
{
- OSStatus r;
-
- if (stm->output_unit) {
- AudioDeviceID output_dev_id;
- r = audiounit_get_output_device_id(&output_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
+ OSStatus rv;
+ // Failing to uninstall listeners is not a fatal error.
+ int r = CUBEB_OK;
- r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
- if (r != noErr) {
- return CUBEB_ERROR;
+ if (stm->output_source_listener) {
+ rv = audiounit_remove_listener(stm->output_source_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/output/"
+ "kAudioDevicePropertyDataSource rv=%d, device id=%d",
+ rv, stm->output_device.id);
+ r = CUBEB_ERROR;
}
+ stm->output_source_listener.reset();
}
- if (stm->input_unit) {
- AudioDeviceID input_dev_id;
- r = audiounit_get_input_device_id(&input_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
+ if (stm->input_source_listener) {
+ rv = audiounit_remove_listener(stm->input_source_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/input/"
+ "kAudioDevicePropertyDataSource rv=%d, device id=%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
}
+ stm->input_source_listener.reset();
+ }
- r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
- if (r != noErr) {
- return CUBEB_ERROR;
+ if (stm->input_alive_listener) {
+ rv = audiounit_remove_listener(stm->input_alive_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/input/"
+ "kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
}
+ stm->input_alive_listener.reset();
}
- return CUBEB_OK;
+
+ return r;
}
static int
@@ -864,20 +1169,20 @@ audiounit_uninstall_system_changed_callback(cubeb_stream * stm)
{
OSStatus r;
- if (stm->output_unit) {
- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
+ if (stm->default_output_listener) {
+ r = audiounit_remove_listener(stm->default_output_listener.get());
if (r != noErr) {
return CUBEB_ERROR;
}
+ stm->default_output_listener.reset();
}
- if (stm->input_unit) {
- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
+ if (stm->default_input_listener) {
+ r = audiounit_remove_listener(stm->default_input_listener.get());
if (r != noErr) {
return CUBEB_ERROR;
}
+ stm->default_input_listener.reset();
}
return CUBEB_OK;
}
@@ -890,12 +1195,11 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
OSStatus r;
AudioDeviceID output_device_id;
AudioObjectPropertyAddress output_device_buffer_size_range = {
- kAudioDevicePropertyBufferFrameSizeRange,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
+ kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster};
- if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
LOG("Could not get default output device id.");
return CUBEB_ERROR;
}
@@ -904,13 +1208,10 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
size = sizeof(*latency_range);
r = AudioObjectGetPropertyData(output_device_id,
- &output_device_buffer_size_range,
- 0,
- NULL,
- &size,
- latency_range);
+ &output_device_buffer_size_range, 0, NULL,
+ &size, latency_range);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectGetPropertyData/buffer size range", r);
+ LOG("AudioObjectGetPropertyData/buffer size range rv=%d", r);
return CUBEB_ERROR;
}
@@ -921,20 +1222,19 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
static AudioObjectID
audiounit_get_default_device_id(cubeb_device_type type)
{
- AudioObjectPropertyAddress adr = { 0, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
- AudioDeviceID devid;
- UInt32 size;
-
+ const AudioObjectPropertyAddress * adr;
if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
- adr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+ adr = &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS;
} else if (type == CUBEB_DEVICE_TYPE_INPUT) {
- adr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
+ adr = &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS;
} else {
return kAudioObjectUnknown;
}
- size = sizeof(AudioDeviceID);
- if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devid) != noErr) {
+ AudioDeviceID devid;
+ UInt32 size = sizeof(AudioDeviceID);
+ if (AudioObjectGetPropertyData(kAudioObjectSystemObject, adr, 0, NULL, &size,
+ &devid) != noErr) {
return kAudioObjectUnknown;
}
@@ -945,7 +1245,7 @@ int
audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{
#if TARGET_OS_IPHONE
- //TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels]
+ // TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels]
*max_channels = 2;
#else
UInt32 size;
@@ -953,27 +1253,22 @@ audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
AudioDeviceID output_device_id;
AudioStreamBasicDescription stream_format;
AudioObjectPropertyAddress stream_format_address = {
- kAudioDevicePropertyStreamFormat,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
+ kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster};
assert(ctx && max_channels);
- if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
return CUBEB_ERROR;
}
size = sizeof(stream_format);
- r = AudioObjectGetPropertyData(output_device_id,
- &stream_format_address,
- 0,
- NULL,
- &size,
- &stream_format);
+ r = AudioObjectGetPropertyData(output_device_id, &stream_format_address, 0,
+ NULL, &size, &stream_format);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectPropertyAddress/StreamFormat", r);
+ LOG("AudioObjectPropertyAddress/StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
@@ -983,12 +1278,11 @@ audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
}
static int
-audiounit_get_min_latency(cubeb * /* ctx */,
- cubeb_stream_params /* params */,
+audiounit_get_min_latency(cubeb * /* ctx */, cubeb_stream_params /* params */,
uint32_t * latency_frames)
{
#if TARGET_OS_IPHONE
- //TODO: [[AVAudioSession sharedInstance] inputLatency]
+ // TODO: [[AVAudioSession sharedInstance] inputLatency]
return CUBEB_ERROR_NOT_SUPPORTED;
#else
AudioValueRange latency_range;
@@ -997,8 +1291,8 @@ audiounit_get_min_latency(cubeb * /* ctx */,
return CUBEB_ERROR;
}
- *latency_frames = std::max<uint32_t>(latency_range.mMinimum,
- SAFE_MIN_LATENCY_FRAMES);
+ *latency_frames =
+ max<uint32_t>(latency_range.mMinimum, SAFE_MIN_LATENCY_FRAMES);
#endif
return CUBEB_OK;
@@ -1008,7 +1302,7 @@ static int
audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate)
{
#if TARGET_OS_IPHONE
- //TODO
+ // TODO
return CUBEB_ERROR_NOT_SUPPORTED;
#else
UInt32 size;
@@ -1016,22 +1310,17 @@ audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate)
Float64 fsamplerate;
AudioDeviceID output_device_id;
AudioObjectPropertyAddress samplerate_address = {
- kAudioDevicePropertyNominalSampleRate,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
+ kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
- if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
return CUBEB_ERROR;
}
size = sizeof(fsamplerate);
- r = AudioObjectGetPropertyData(output_device_id,
- &samplerate_address,
- 0,
- NULL,
- &size,
- &fsamplerate);
+ r = AudioObjectGetPropertyData(output_device_id, &samplerate_address, 0, NULL,
+ &size, &fsamplerate);
if (r != noErr) {
return CUBEB_ERROR;
@@ -1042,27 +1331,133 @@ audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate)
return CUBEB_OK;
}
-static OSStatus audiounit_remove_device_listener(cubeb * context);
+static cubeb_channel_layout
+audiounit_convert_channel_layout(AudioChannelLayout * layout)
+{
+ // When having one or two channel, force mono or stereo. Some devices (namely,
+ // Bose QC35, mark 1 and 2), expose a single channel mapped to the right for
+ // some reason.
+ if (layout->mNumberChannelDescriptions == 1) {
+ return CUBEB_LAYOUT_MONO;
+ } else if (layout->mNumberChannelDescriptions == 2) {
+ return CUBEB_LAYOUT_STEREO;
+ }
+
+ if (layout->mChannelLayoutTag !=
+ kAudioChannelLayoutTag_UseChannelDescriptions) {
+ // kAudioChannelLayoutTag_UseChannelBitmap
+ // kAudioChannelLayoutTag_Mono
+ // kAudioChannelLayoutTag_Stereo
+ // ....
+ LOG("Only handle UseChannelDescriptions for now.\n");
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ cubeb_channel_layout cl = 0;
+ for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) {
+ cubeb_channel cc = channel_label_to_cubeb_channel(
+ layout->mChannelDescriptions[i].mChannelLabel);
+ if (cc == CHANNEL_UNKNOWN) {
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+ cl |= cc;
+ }
+
+ return cl;
+}
+
+static cubeb_channel_layout
+audiounit_get_preferred_channel_layout(AudioUnit output_unit)
+{
+ OSStatus rv = noErr;
+ UInt32 size = 0;
+ rv = AudioUnitGetPropertyInfo(
+ output_unit, kAudioDevicePropertyPreferredChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, &size, nullptr);
+ if (rv != noErr) {
+ LOG("AudioUnitGetPropertyInfo/kAudioDevicePropertyPreferredChannelLayout "
+ "rv=%d",
+ rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+ assert(size > 0);
+
+ auto layout = make_sized_audio_channel_layout(size);
+ rv = AudioUnitGetProperty(
+ output_unit, kAudioDevicePropertyPreferredChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, layout.get(), &size);
+ if (rv != noErr) {
+ LOG("AudioUnitGetProperty/kAudioDevicePropertyPreferredChannelLayout rv=%d",
+ rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ return audiounit_convert_channel_layout(layout.get());
+}
+
+static cubeb_channel_layout
+audiounit_get_current_channel_layout(AudioUnit output_unit)
+{
+ OSStatus rv = noErr;
+ UInt32 size = 0;
+ rv = AudioUnitGetPropertyInfo(
+ output_unit, kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, &size, nullptr);
+ if (rv != noErr) {
+ LOG("AudioUnitGetPropertyInfo/kAudioUnitProperty_AudioChannelLayout rv=%d",
+ rv);
+ // This property isn't known before macOS 10.12, attempt another method.
+ return audiounit_get_preferred_channel_layout(output_unit);
+ }
+ assert(size > 0);
+
+ auto layout = make_sized_audio_channel_layout(size);
+ rv = AudioUnitGetProperty(output_unit, kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, layout.get(),
+ &size);
+ if (rv != noErr) {
+ LOG("AudioUnitGetProperty/kAudioUnitProperty_AudioChannelLayout rv=%d", rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ return audiounit_convert_channel_layout(layout.get());
+}
+
+static int
+audiounit_create_unit(AudioUnit * unit, device_info * device);
+
+static OSStatus
+audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype);
static void
audiounit_destroy(cubeb * ctx)
{
- // Disabling this assert for bug 1083664 -- we seem to leak a stream
- // assert(ctx->active_streams == 0);
-
{
auto_lock lock(ctx->mutex);
+
+ // Disabling this assert for bug 1083664 -- we seem to leak a stream
+ // assert(ctx->active_streams == 0);
+ if (audiounit_active_streams(ctx) > 0) {
+ LOG("(%p) API misuse, %d streams active when context destroyed!", ctx,
+ audiounit_active_streams(ctx));
+ }
+
/* Unregister the callback if necessary. */
- if(ctx->collection_changed_callback) {
- audiounit_remove_device_listener(ctx);
+ if (ctx->input_collection_changed_callback) {
+ audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT);
+ }
+ if (ctx->output_collection_changed_callback) {
+ audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
}
}
- ctx->~cubeb();
- free(ctx);
+ dispatch_release(ctx->serial_queue);
+
+ delete ctx;
}
-static void audiounit_stream_destroy(cubeb_stream * stm);
+static void
+audiounit_stream_destroy(cubeb_stream * stm);
static int
audio_stream_desc_init(AudioStreamBasicDescription * ss,
@@ -1075,8 +1470,8 @@ audio_stream_desc_init(AudioStreamBasicDescription * ss,
break;
case CUBEB_SAMPLE_S16BE:
ss->mBitsPerChannel = 16;
- ss->mFormatFlags = kAudioFormatFlagIsSignedInteger |
- kAudioFormatFlagIsBigEndian;
+ ss->mFormatFlags =
+ kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian;
break;
case CUBEB_SAMPLE_FLOAT32LE:
ss->mBitsPerChannel = 32;
@@ -1084,8 +1479,7 @@ audio_stream_desc_init(AudioStreamBasicDescription * ss,
break;
case CUBEB_SAMPLE_FLOAT32BE:
ss->mBitsPerChannel = 32;
- ss->mFormatFlags = kAudioFormatFlagIsFloat |
- kAudioFormatFlagIsBigEndian;
+ ss->mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsBigEndian;
break;
default:
return CUBEB_ERROR_INVALID_FORMAT;
@@ -1105,29 +1499,543 @@ audio_stream_desc_init(AudioStreamBasicDescription * ss,
return CUBEB_OK;
}
+void
+audiounit_init_mixer(cubeb_stream * stm)
+{
+ // We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio
+ // data, it silently drop the channels so we need to remix the
+ // audio data by ourselves to keep all the information.
+ stm->mixer.reset(cubeb_mixer_create(
+ stm->output_stream_params.format, stm->output_stream_params.channels,
+ stm->output_stream_params.layout, stm->context->channels,
+ stm->context->layout));
+ assert(stm->mixer);
+}
+
+static int
+audiounit_set_channel_layout(AudioUnit unit, io_side side,
+ cubeb_channel_layout layout)
+{
+ if (side != io_side::OUTPUT) {
+ return CUBEB_ERROR;
+ }
+
+ if (layout == CUBEB_LAYOUT_UNDEFINED) {
+ // We leave everything as-is...
+ return CUBEB_OK;
+ }
+
+ OSStatus r;
+ uint32_t nb_channels = cubeb_channel_layout_nb_channels(layout);
+
+ // We do not use CoreAudio standard layout for lack of documentation on what
+ // the actual channel orders are. So we set a custom layout.
+ size_t size = offsetof(AudioChannelLayout, mChannelDescriptions[nb_channels]);
+ auto au_layout = make_sized_audio_channel_layout(size);
+ au_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+ au_layout->mNumberChannelDescriptions = nb_channels;
+
+ uint32_t channels = 0;
+ cubeb_channel_layout channelMap = layout;
+ for (uint32_t i = 0; channelMap != 0; ++i) {
+ XASSERT(channels < nb_channels);
+ uint32_t channel = (channelMap & 1) << i;
+ if (channel != 0) {
+ au_layout->mChannelDescriptions[channels].mChannelLabel =
+ cubeb_channel_to_channel_label(static_cast<cubeb_channel>(channel));
+ au_layout->mChannelDescriptions[channels].mChannelFlags =
+ kAudioChannelFlags_AllOff;
+ channels++;
+ }
+ channelMap = channelMap >> 1;
+ }
+
+ r = AudioUnitSetProperty(unit, kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Input, AU_OUT_BUS, au_layout.get(),
+ size);
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/%s/kAudioUnitProperty_AudioChannelLayout rv=%d",
+ to_string(side), r);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+void
+audiounit_layout_init(cubeb_stream * stm, io_side side)
+{
+ // We currently don't support the input layout setting.
+ if (side == io_side::INPUT) {
+ return;
+ }
+
+ stm->context->layout = audiounit_get_current_channel_layout(stm->output_unit);
+
+ audiounit_set_channel_layout(stm->output_unit, io_side::OUTPUT,
+ stm->context->layout);
+}
+
+static vector<AudioObjectID>
+audiounit_get_sub_devices(AudioDeviceID device_id)
+{
+ vector<AudioDeviceID> sub_devices;
+ AudioObjectPropertyAddress property_address = {
+ kAudioAggregateDevicePropertyActiveSubDeviceList,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+ UInt32 size = 0;
+ OSStatus rv = AudioObjectGetPropertyDataSize(device_id, &property_address, 0,
+ nullptr, &size);
+
+ if (rv != noErr) {
+ sub_devices.push_back(device_id);
+ return sub_devices;
+ }
+
+ uint32_t count = static_cast<uint32_t>(size / sizeof(AudioObjectID));
+ sub_devices.resize(count);
+ rv = AudioObjectGetPropertyData(device_id, &property_address, 0, nullptr,
+ &size, sub_devices.data());
+ if (rv != noErr) {
+ sub_devices.clear();
+ sub_devices.push_back(device_id);
+ } else {
+ LOG("Found %u sub-devices", count);
+ }
+ return sub_devices;
+}
+
+static int
+audiounit_create_blank_aggregate_device(AudioObjectID * plugin_id,
+ AudioDeviceID * aggregate_device_id)
+{
+ AudioObjectPropertyAddress address_plugin_bundle_id = {
+ kAudioHardwarePropertyPlugInForBundleID, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size = 0;
+ OSStatus r = AudioObjectGetPropertyDataSize(
+ kAudioObjectSystemObject, &address_plugin_bundle_id, 0, NULL, &size);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/"
+ "kAudioHardwarePropertyPlugInForBundleID, rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ AudioValueTranslation translation_value;
+ CFStringRef in_bundle_ref = CFSTR("com.apple.audio.CoreAudio");
+ translation_value.mInputData = &in_bundle_ref;
+ translation_value.mInputDataSize = sizeof(in_bundle_ref);
+ translation_value.mOutputData = plugin_id;
+ translation_value.mOutputDataSize = sizeof(*plugin_id);
+
+ r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &address_plugin_bundle_id, 0, nullptr, &size,
+ &translation_value);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioHardwarePropertyPlugInForBundleID, "
+ "rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ AudioObjectPropertyAddress create_aggregate_device_address = {
+ kAudioPlugInCreateAggregateDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ r = AudioObjectGetPropertyDataSize(
+ *plugin_id, &create_aggregate_device_address, 0, nullptr, &size);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioPlugInCreateAggregateDevice, "
+ "rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ struct timeval timestamp;
+ gettimeofday(&timestamp, NULL);
+ long long int time_id = timestamp.tv_sec * 1000000LL + timestamp.tv_usec;
+ CFStringRef aggregate_device_name = CFStringCreateWithFormat(
+ NULL, NULL, CFSTR("%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceNameKey),
+ aggregate_device_name);
+ CFRelease(aggregate_device_name);
+
+ CFStringRef aggregate_device_UID =
+ CFStringCreateWithFormat(NULL, NULL, CFSTR("org.mozilla.%s_%llx"),
+ PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceUIDKey),
+ aggregate_device_UID);
+ CFRelease(aggregate_device_UID);
+
+ int private_value = 1;
+ CFNumberRef aggregate_device_private_key =
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_value);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceIsPrivateKey),
+ aggregate_device_private_key);
+ CFRelease(aggregate_device_private_key);
+
+ int stacked_value = 0;
+ CFNumberRef aggregate_device_stacked_key =
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &stacked_value);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceIsStackedKey),
+ aggregate_device_stacked_key);
+ CFRelease(aggregate_device_stacked_key);
+
+ r = AudioObjectGetPropertyData(*plugin_id, &create_aggregate_device_address,
+ sizeof(aggregate_device_dict),
+ &aggregate_device_dict, &size,
+ aggregate_device_id);
+ CFRelease(aggregate_device_dict);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioPlugInCreateAggregateDevice, rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+ LOG("New aggregate device %u", *aggregate_device_id);
+
+ return CUBEB_OK;
+}
+
+// The returned CFStringRef object needs to be released (via CFRelease)
+// if it's not NULL, since the reference count of the returned CFStringRef
+// object is increased.
+static CFStringRef
+get_device_name(AudioDeviceID id)
+{
+ UInt32 size = sizeof(CFStringRef);
+ CFStringRef UIname = nullptr;
+ AudioObjectPropertyAddress address_uuid = {kAudioDevicePropertyDeviceUID,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ OSStatus err =
+ AudioObjectGetPropertyData(id, &address_uuid, 0, nullptr, &size, &UIname);
+ return (err == noErr) ? UIname : NULL;
+}
+
+static int
+audiounit_set_aggregate_sub_device_list(AudioDeviceID aggregate_device_id,
+ AudioDeviceID input_device_id,
+ AudioDeviceID output_device_id)
+{
+ LOG("Add devices input %u and output %u into aggregate device %u",
+ input_device_id, output_device_id, aggregate_device_id);
+ const vector<AudioDeviceID> output_sub_devices =
+ audiounit_get_sub_devices(output_device_id);
+ const vector<AudioDeviceID> input_sub_devices =
+ audiounit_get_sub_devices(input_device_id);
+
+ CFMutableArrayRef aggregate_sub_devices_array =
+ CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ /* The order of the items in the array is significant and is used to determine
+ the order of the streams of the AudioAggregateDevice. */
+ for (UInt32 i = 0; i < output_sub_devices.size(); i++) {
+ CFStringRef ref = get_device_name(output_sub_devices[i]);
+ if (ref == NULL) {
+ CFRelease(aggregate_sub_devices_array);
+ return CUBEB_ERROR;
+ }
+ CFArrayAppendValue(aggregate_sub_devices_array, ref);
+ CFRelease(ref);
+ }
+ for (UInt32 i = 0; i < input_sub_devices.size(); i++) {
+ CFStringRef ref = get_device_name(input_sub_devices[i]);
+ if (ref == NULL) {
+ CFRelease(aggregate_sub_devices_array);
+ return CUBEB_ERROR;
+ }
+ CFArrayAppendValue(aggregate_sub_devices_array, ref);
+ CFRelease(ref);
+ }
+
+ AudioObjectPropertyAddress aggregate_sub_device_list = {
+ kAudioAggregateDevicePropertyFullSubDeviceList,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+ UInt32 size = sizeof(CFMutableArrayRef);
+ OSStatus rv = AudioObjectSetPropertyData(
+ aggregate_device_id, &aggregate_sub_device_list, 0, nullptr, size,
+ &aggregate_sub_devices_array);
+ CFRelease(aggregate_sub_devices_array);
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/"
+ "kAudioAggregateDevicePropertyFullSubDeviceList, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_set_master_aggregate_device(const AudioDeviceID aggregate_device_id)
+{
+ assert(aggregate_device_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress master_aggregate_sub_device = {
+ kAudioAggregateDevicePropertyMasterSubDevice,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+
+ // Master become the 1st output sub device
+ AudioDeviceID output_device_id =
+ audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ const vector<AudioDeviceID> output_sub_devices =
+ audiounit_get_sub_devices(output_device_id);
+ CFStringRef master_sub_device = get_device_name(output_sub_devices[0]);
+
+ UInt32 size = sizeof(CFStringRef);
+ OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id,
+ &master_aggregate_sub_device, 0,
+ NULL, size, &master_sub_device);
+ if (master_sub_device) {
+ CFRelease(master_sub_device);
+ }
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/"
+ "kAudioAggregateDevicePropertyMasterSubDevice, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_activate_clock_drift_compensation(
+ const AudioDeviceID aggregate_device_id)
+{
+ assert(aggregate_device_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress address_owned = {
+ kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ UInt32 qualifier_data_size = sizeof(AudioObjectID);
+ AudioClassID class_id = kAudioSubDeviceClassID;
+ void * qualifier_data = &class_id;
+ UInt32 size = 0;
+ OSStatus rv = AudioObjectGetPropertyDataSize(
+ aggregate_device_id, &address_owned, qualifier_data_size, qualifier_data,
+ &size);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioObjectPropertyOwnedObjects, "
+ "rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ UInt32 subdevices_num = 0;
+ subdevices_num = size / sizeof(AudioObjectID);
+ AudioObjectID sub_devices[subdevices_num];
+ size = sizeof(sub_devices);
+
+ rv = AudioObjectGetPropertyData(aggregate_device_id, &address_owned,
+ qualifier_data_size, qualifier_data, &size,
+ sub_devices);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioObjectPropertyOwnedObjects, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ AudioObjectPropertyAddress address_drift = {
+ kAudioSubDevicePropertyDriftCompensation, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ // Start from the second device since the first is the master clock
+ for (UInt32 i = 1; i < subdevices_num; ++i) {
+ UInt32 drift_compensation_value = 1;
+ rv = AudioObjectSetPropertyData(sub_devices[i], &address_drift, 0, nullptr,
+ sizeof(UInt32), &drift_compensation_value);
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/"
+ "kAudioSubDevicePropertyDriftCompensation, rv=%d",
+ rv);
+ return CUBEB_OK;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+audiounit_destroy_aggregate_device(AudioObjectID plugin_id,
+ AudioDeviceID * aggregate_device_id);
+static void
+audiounit_get_available_samplerate(AudioObjectID devid,
+ AudioObjectPropertyScope scope,
+ uint32_t * min, uint32_t * max,
+ uint32_t * def);
+static int
+audiounit_create_device_from_hwdev(cubeb_device_info * dev_info,
+ AudioObjectID devid, cubeb_device_type type);
+static void
+audiounit_device_destroy(cubeb_device_info * device);
+
+static void
+audiounit_workaround_for_airpod(cubeb_stream * stm)
+{
+ cubeb_device_info input_device_info;
+ audiounit_create_device_from_hwdev(&input_device_info, stm->input_device.id,
+ CUBEB_DEVICE_TYPE_INPUT);
+
+ cubeb_device_info output_device_info;
+ audiounit_create_device_from_hwdev(&output_device_info, stm->output_device.id,
+ CUBEB_DEVICE_TYPE_OUTPUT);
+
+ std::string input_name_str(input_device_info.friendly_name);
+ std::string output_name_str(output_device_info.friendly_name);
+
+ if (input_name_str.find("AirPods") != std::string::npos &&
+ output_name_str.find("AirPods") != std::string::npos) {
+ uint32_t input_min_rate = 0;
+ uint32_t input_max_rate = 0;
+ uint32_t input_nominal_rate = 0;
+ audiounit_get_available_samplerate(
+ stm->input_device.id, kAudioObjectPropertyScopeGlobal, &input_min_rate,
+ &input_max_rate, &input_nominal_rate);
+ LOG("(%p) Input device %u, name: %s, min: %u, max: %u, nominal rate: %u",
+ stm, stm->input_device.id, input_device_info.friendly_name,
+ input_min_rate, input_max_rate, input_nominal_rate);
+ uint32_t output_min_rate = 0;
+ uint32_t output_max_rate = 0;
+ uint32_t output_nominal_rate = 0;
+ audiounit_get_available_samplerate(
+ stm->output_device.id, kAudioObjectPropertyScopeGlobal,
+ &output_min_rate, &output_max_rate, &output_nominal_rate);
+ LOG("(%p) Output device %u, name: %s, min: %u, max: %u, nominal rate: %u",
+ stm, stm->output_device.id, output_device_info.friendly_name,
+ output_min_rate, output_max_rate, output_nominal_rate);
+
+ Float64 rate = input_nominal_rate;
+ AudioObjectPropertyAddress addr = {kAudioDevicePropertyNominalSampleRate,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ OSStatus rv = AudioObjectSetPropertyData(stm->aggregate_device_id, &addr, 0,
+ nullptr, sizeof(Float64), &rate);
+ if (rv != noErr) {
+ LOG("Non fatal error, "
+ "AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, "
+ "rv=%d",
+ rv);
+ }
+ }
+ audiounit_device_destroy(&input_device_info);
+ audiounit_device_destroy(&output_device_info);
+}
+
+/*
+ * Aggregate Device is a virtual audio interface which utilizes inputs and
+ * outputs of one or more physical audio interfaces. It is possible to use the
+ * clock of one of the devices as a master clock for all the combined devices
+ * and enable drift compensation for the devices that are not designated clock
+ * master.
+ *
+ * Creating a new aggregate device programmatically requires [0][1]:
+ * 1. Locate the base plug-in ("com.apple.audio.CoreAudio")
+ * 2. Create a dictionary that describes the aggregate device
+ * (don't add sub-devices in that step, prone to fail [0])
+ * 3. Ask the base plug-in to create the aggregate device (blank)
+ * 4. Add the array of sub-devices.
+ * 5. Set the master device (1st output device in our case)
+ * 6. Enable drift compensation for the non-master devices
+ *
+ * [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html
+ * [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html
+ * [2] CoreAudio.framework/Headers/AudioHardware.h
+ * */
+static int
+audiounit_create_aggregate_device(cubeb_stream * stm)
+{
+ int r = audiounit_create_blank_aggregate_device(&stm->plugin_id,
+ &stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to create blank aggregate device", stm);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_set_aggregate_sub_device_list(
+ stm->aggregate_device_id, stm->input_device.id, stm->output_device.id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to set aggregate sub-device list", stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_set_master_aggregate_device(stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to set master sub-device for aggregate device", stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to activate clock drift compensation for aggregate device",
+ stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ audiounit_workaround_for_airpod(stm);
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_destroy_aggregate_device(AudioObjectID plugin_id,
+ AudioDeviceID * aggregate_device_id)
+{
+ assert(aggregate_device_id && *aggregate_device_id != kAudioDeviceUnknown &&
+ plugin_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress destroy_aggregate_device_addr = {
+ kAudioPlugInDestroyAggregateDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size;
+ OSStatus rv = AudioObjectGetPropertyDataSize(
+ plugin_id, &destroy_aggregate_device_addr, 0, NULL, &size);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioPlugInDestroyAggregateDevice, "
+ "rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ rv = AudioObjectGetPropertyData(plugin_id, &destroy_aggregate_device_addr, 0,
+ NULL, &size, aggregate_device_id);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ LOG("Destroyed aggregate device %d", *aggregate_device_id);
+ *aggregate_device_id = kAudioObjectUnknown;
+ return CUBEB_OK;
+}
+
static int
-audiounit_create_unit(AudioUnit * unit,
- bool is_input,
- const cubeb_stream_params * /* stream_params */,
- cubeb_devid device)
+audiounit_new_unit_instance(AudioUnit * unit, device_info * device)
{
AudioComponentDescription desc;
AudioComponent comp;
- UInt32 enable;
- AudioDeviceID devid;
OSStatus rv;
desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IPHONE
- bool use_default_output = false;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#else
// Use the DefaultOutputUnit for output when no device is specified
// so we retain automatic output device switching when the default
// changes. Once we have complete support for device notifications
// and switching, we can use the AUHAL for everything.
- bool use_default_output = device == NULL && !is_input;
- if (use_default_output) {
+ if ((device->flags & DEV_SYSTEM_DEFAULT) && (device->flags & DEV_OUTPUT)) {
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
} else {
desc.componentSubType = kAudioUnitSubType_HALOutput;
@@ -1144,96 +2052,116 @@ audiounit_create_unit(AudioUnit * unit,
rv = AudioComponentInstanceNew(comp, unit);
if (rv != noErr) {
- PRINT_ERROR_CODE("AudioComponentInstanceNew", rv);
+ LOG("AudioComponentInstanceNew rv=%d", rv);
return CUBEB_ERROR;
}
+ return CUBEB_OK;
+}
- if (!use_default_output) {
- enable = 1;
- rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,
- is_input ? kAudioUnitScope_Input : kAudioUnitScope_Output,
- is_input ? AU_IN_BUS : AU_OUT_BUS, &enable, sizeof(UInt32));
- if (rv != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO", rv);
- return CUBEB_ERROR;
- }
-
- enable = 0;
- rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,
- is_input ? kAudioUnitScope_Output : kAudioUnitScope_Input,
- is_input ? AU_OUT_BUS : AU_IN_BUS, &enable, sizeof(UInt32));
- if (rv != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO", rv);
- return CUBEB_ERROR;
- }
+enum enable_state {
+ DISABLE,
+ ENABLE,
+};
- if (device == NULL) {
- assert(is_input);
- devid = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
- } else {
- devid = reinterpret_cast<intptr_t>(device);
- }
- rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global,
- is_input ? AU_IN_BUS : AU_OUT_BUS,
- &devid, sizeof(AudioDeviceID));
- if (rv != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice", rv);
- return CUBEB_ERROR;
- }
+static int
+audiounit_enable_unit_scope(AudioUnit * unit, io_side side, enable_state state)
+{
+ OSStatus rv;
+ UInt32 enable = state;
+ rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,
+ (side == io_side::INPUT) ? kAudioUnitScope_Input
+ : kAudioUnitScope_Output,
+ (side == io_side::INPUT) ? AU_IN_BUS : AU_OUT_BUS,
+ &enable, sizeof(UInt32));
+ if (rv != noErr) {
+ LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO rv=%d", rv);
+ return CUBEB_ERROR;
}
-
return CUBEB_OK;
}
static int
-audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)
+audiounit_create_unit(AudioUnit * unit, device_info * device)
{
- if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) {
- stream->input_linear_buffer = new auto_array_wrapper(
- new auto_array<short>(capacity *
- stream->input_buffer_frames *
- stream->input_desc.mChannelsPerFrame) );
- } else {
- stream->input_linear_buffer = new auto_array_wrapper(
- new auto_array<float>(capacity *
- stream->input_buffer_frames *
- stream->input_desc.mChannelsPerFrame) );
- }
+ assert(*unit == nullptr);
+ assert(device);
- if (!stream->input_linear_buffer) {
- return CUBEB_ERROR;
+ OSStatus rv;
+ int r;
+
+ r = audiounit_new_unit_instance(unit, device);
+ if (r != CUBEB_OK) {
+ return r;
}
+ assert(*unit);
- assert(stream->input_linear_buffer->length() == 0);
+ if ((device->flags & DEV_SYSTEM_DEFAULT) && (device->flags & DEV_OUTPUT)) {
+ return CUBEB_OK;
+ }
- // Pre-buffer silence if needed
- if (capacity != 1) {
- size_t silence_size = stream->input_buffer_frames *
- stream->input_desc.mChannelsPerFrame;
- stream->input_linear_buffer->push_silence(silence_size);
+ if (device->flags & DEV_INPUT) {
+ r = audiounit_enable_unit_scope(unit, io_side::INPUT, ENABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to enable audiounit input scope");
+ return r;
+ }
+ r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, DISABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to disable audiounit output scope");
+ return r;
+ }
+ } else if (device->flags & DEV_OUTPUT) {
+ r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, ENABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to enable audiounit output scope");
+ return r;
+ }
+ r = audiounit_enable_unit_scope(unit, io_side::INPUT, DISABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to disable audiounit input scope");
+ return r;
+ }
+ } else {
+ assert(false);
+ }
- assert(stream->input_linear_buffer->length() == silence_size);
+ rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, 0, &device->id,
+ sizeof(AudioDeviceID));
+ if (rv != noErr) {
+ LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d",
+ rv);
+ return CUBEB_ERROR;
}
return CUBEB_OK;
}
-static void
-audiounit_destroy_input_linear_buffer(cubeb_stream * stream)
+static int
+audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)
{
- delete stream->input_linear_buffer;
+ uint32_t size =
+ capacity * stream->latency_frames * stream->input_desc.mChannelsPerFrame;
+ if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) {
+ stream->input_linear_buffer.reset(new auto_array_wrapper_impl<short>(size));
+ } else {
+ stream->input_linear_buffer.reset(new auto_array_wrapper_impl<float>(size));
+ }
+ assert(stream->input_linear_buffer->length() == 0);
+
+ return CUBEB_OK;
}
static uint32_t
audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
{
// For the 1st stream set anything within safe min-max
- assert(stm->context->active_streams > 0);
- if (stm->context->active_streams == 1) {
- return std::max(std::min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
- SAFE_MIN_LATENCY_FRAMES);
+ assert(audiounit_active_streams(stm->context) > 0);
+ if (audiounit_active_streams(stm->context) == 1) {
+ return max(min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
}
+ assert(stm->output_unit);
// If more than one stream operates in parallel
// allow only lower values of latency
@@ -1241,42 +2169,42 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
UInt32 output_buffer_size = 0;
UInt32 size = sizeof(output_buffer_size);
if (stm->output_unit) {
- r = AudioUnitGetProperty(stm->output_unit,
- kAudioDevicePropertyBufferFrameSize,
- kAudioUnitScope_Output,
- AU_OUT_BUS,
- &output_buffer_size,
- &size);
+ r = AudioUnitGetProperty(
+ stm->output_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output, AU_OUT_BUS, &output_buffer_size, &size);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
+ LOG("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ r);
return 0;
}
- output_buffer_size = std::max(std::min<uint32_t>(output_buffer_size, SAFE_MAX_LATENCY_FRAMES),
- SAFE_MIN_LATENCY_FRAMES);
+ output_buffer_size =
+ max(min<uint32_t>(output_buffer_size, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
}
UInt32 input_buffer_size = 0;
if (stm->input_unit) {
- r = AudioUnitGetProperty(stm->input_unit,
- kAudioDevicePropertyBufferFrameSize,
- kAudioUnitScope_Input,
- AU_IN_BUS,
- &input_buffer_size,
- &size);
+ r = AudioUnitGetProperty(
+ stm->input_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Input, AU_IN_BUS, &input_buffer_size, &size);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
+ LOG("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ r);
return 0;
}
- input_buffer_size = std::max(std::min<uint32_t>(input_buffer_size, SAFE_MAX_LATENCY_FRAMES),
- SAFE_MIN_LATENCY_FRAMES);
+ input_buffer_size =
+ max(min<uint32_t>(input_buffer_size, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
}
// Every following active streams can only set smaller latency
UInt32 upper_latency_limit = 0;
if (input_buffer_size != 0 && output_buffer_size != 0) {
- upper_latency_limit = std::min<uint32_t>(input_buffer_size, output_buffer_size);
+ upper_latency_limit = min<uint32_t>(input_buffer_size, output_buffer_size);
} else if (input_buffer_size != 0) {
upper_latency_limit = input_buffer_size;
} else if (output_buffer_size != 0) {
@@ -1285,8 +2213,8 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
upper_latency_limit = SAFE_MAX_LATENCY_FRAMES;
}
- return std::max(std::min<uint32_t>(latency_frames, upper_latency_limit),
- SAFE_MIN_LATENCY_FRAMES);
+ return max(min<uint32_t>(latency_frames, upper_latency_limit),
+ SAFE_MIN_LATENCY_FRAMES);
}
/*
@@ -1298,130 +2226,103 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
* - property has changed, remove the listener
* */
static void
-buffer_size_changed_callback(void * inClientData,
- AudioUnit inUnit,
- AudioUnitPropertyID inPropertyID,
- AudioUnitScope inScope,
- AudioUnitElement inElement)
+buffer_size_changed_callback(void * inClientData, AudioUnit inUnit,
+ AudioUnitPropertyID inPropertyID,
+ AudioUnitScope inScope, AudioUnitElement inElement)
{
cubeb_stream * stm = (cubeb_stream *)inClientData;
AudioUnit au = inUnit;
AudioUnitScope au_scope = kAudioUnitScope_Input;
AudioUnitElement au_element = inElement;
- const char * au_type = "output";
+ char const * au_type = "output";
- if (au == stm->input_unit) {
+ if (AU_IN_BUS == inElement) {
au_scope = kAudioUnitScope_Output;
au_type = "input";
}
switch (inPropertyID) {
- case kAudioDevicePropertyBufferFrameSize: {
- if (inScope != au_scope) {
- break;
- }
- UInt32 new_buffer_size;
- UInt32 outSize = sizeof(UInt32);
- OSStatus r = AudioUnitGetProperty(au,
- kAudioDevicePropertyBufferFrameSize,
- au_scope,
- au_element,
- &new_buffer_size,
- &outSize);
- if (r != noErr) {
- LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm);
- } else {
- LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm,
- au_type, new_buffer_size, inScope);
- }
- stm->buffer_size_change_state = true;
+ case kAudioDevicePropertyBufferFrameSize: {
+ if (inScope != au_scope) {
break;
}
+ UInt32 new_buffer_size;
+ UInt32 outSize = sizeof(UInt32);
+ OSStatus r =
+ AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope,
+ au_element, &new_buffer_size, &outSize);
+ if (r != noErr) {
+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current "
+ "buffer size",
+ stm);
+ } else {
+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size "
+ "= %d for scope %d",
+ stm, au_type, new_buffer_size, inScope);
+ }
+ stm->buffer_size_change_state = true;
+ break;
+ }
}
}
-enum set_buffer_size_side {
- INPUT,
- OUTPUT,
-};
-
static int
-audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side)
+audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames,
+ io_side side)
{
AudioUnit au = stm->output_unit;
AudioUnitScope au_scope = kAudioUnitScope_Input;
AudioUnitElement au_element = AU_OUT_BUS;
- const char * au_type = "output";
- if (set_side == INPUT) {
+ if (side == io_side::INPUT) {
au = stm->input_unit;
au_scope = kAudioUnitScope_Output;
au_element = AU_IN_BUS;
- au_type = "input";
}
uint32_t buffer_frames = 0;
UInt32 size = sizeof(buffer_frames);
- int r = AudioUnitGetProperty(au,
- kAudioDevicePropertyBufferFrameSize,
- au_scope,
- au_element,
- &buffer_frames,
- &size);
+ int r = AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize,
+ au_scope, au_element, &buffer_frames, &size);
if (r != noErr) {
- if (set_side == INPUT) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
- } else {
- PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
- }
+ LOG("AudioUnitGetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d",
+ to_string(side), r);
return CUBEB_ERROR;
}
if (new_size_frames == buffer_frames) {
- LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames);
+ LOG("(%p) No need to update %s buffer size already %u frames", stm,
+ to_string(side), buffer_frames);
return CUBEB_OK;
}
- r = AudioUnitAddPropertyListener(au,
- kAudioDevicePropertyBufferFrameSize,
- buffer_size_changed_callback,
- stm);
+ r = AudioUnitAddPropertyListener(au, kAudioDevicePropertyBufferFrameSize,
+ buffer_size_changed_callback, stm);
if (r != noErr) {
- if (set_side == INPUT) {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
- } else {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
- }
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ to_string(side), r);
return CUBEB_ERROR;
}
stm->buffer_size_change_state = false;
- r = AudioUnitSetProperty(au,
- kAudioDevicePropertyBufferFrameSize,
- au_scope,
- au_element,
- &new_size_frames,
+ r = AudioUnitSetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope,
+ au_element, &new_size_frames,
sizeof(new_size_frames));
if (r != noErr) {
- if (set_side == INPUT) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
- } else {
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
- }
+ LOG("AudioUnitSetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d",
+ to_string(side), r);
- r = AudioUnitRemovePropertyListenerWithUserData(au,
- kAudioDevicePropertyBufferFrameSize,
- buffer_size_changed_callback,
- stm);
+ r = AudioUnitRemovePropertyListenerWithUserData(
+ au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback,
+ stm);
if (r != noErr) {
- if (set_side == INPUT) {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
- } else {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
- }
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ to_string(side), r);
}
return CUBEB_ERROR;
@@ -1432,22 +2333,21 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buff
struct timespec req, rem;
req.tv_sec = 0;
req.tv_nsec = 100000000L; // 0.1 sec
- if (nanosleep(&req , &rem) < 0 ) {
- LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec);
+ if (nanosleep(&req, &rem) < 0) {
+ LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time "
+ "%ld nano secs \n",
+ stm, rem.tv_nsec);
}
LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count);
}
- r = AudioUnitRemovePropertyListenerWithUserData(au,
- kAudioDevicePropertyBufferFrameSize,
- buffer_size_changed_callback,
- stm);
+ r = AudioUnitRemovePropertyListenerWithUserData(
+ au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback,
+ stm);
if (r != noErr) {
- if (set_side == INPUT) {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
- } else {
- PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
- }
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ to_string(side), r);
return CUBEB_ERROR;
}
@@ -1456,32 +2356,33 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buff
return CUBEB_ERROR;
}
- LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames);
+ LOG("(%p) %s buffer size changed to %u frames.", stm, to_string(side),
+ new_size_frames);
return CUBEB_OK;
}
static int
audiounit_configure_input(cubeb_stream * stm)
{
+ assert(stm && stm->input_unit);
+
int r = 0;
UInt32 size;
AURenderCallbackStruct aurcbs_in;
- LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
+ LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in "
+ "frames %u.",
stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
stm->input_stream_params.format, stm->latency_frames);
/* Get input device sample rate. */
AudioStreamBasicDescription input_hw_desc;
size = sizeof(AudioStreamBasicDescription);
- r = AudioUnitGetProperty(stm->input_unit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
- AU_IN_BUS,
- &input_hw_desc,
+ r = AudioUnitGetProperty(stm->input_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, AU_IN_BUS, &input_hw_desc,
&size);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
+ LOG("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
stm->input_hw_rate = input_hw_desc.mSampleRate;
@@ -1495,8 +2396,7 @@ audiounit_configure_input(cubeb_stream * stm)
}
// Use latency to set buffer size
- stm->input_buffer_frames = stm->latency_frames;
- r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT);
+ r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::INPUT);
if (r != CUBEB_OK) {
LOG("(%p) Error in change input buffer size.", stm);
return CUBEB_ERROR;
@@ -1507,26 +2407,22 @@ audiounit_configure_input(cubeb_stream * stm)
we will resample inside input callback. */
src_desc.mSampleRate = stm->input_hw_rate;
- r = AudioUnitSetProperty(stm->input_unit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output,
- AU_IN_BUS,
- &src_desc,
+ r = AudioUnitSetProperty(stm->input_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output, AU_IN_BUS, &src_desc,
sizeof(AudioStreamBasicDescription));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
+ LOG("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
/* Frames per buffer in the input callback. */
- r = AudioUnitSetProperty(stm->input_unit,
- kAudioUnitProperty_MaximumFramesPerSlice,
- kAudioUnitScope_Global,
- AU_IN_BUS,
- &stm->input_buffer_frames,
- sizeof(UInt32));
+ r = AudioUnitSetProperty(
+ stm->input_unit, kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Global, AU_IN_BUS, &stm->latency_frames, sizeof(UInt32));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
+ LOG("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice "
+ "rv=%d",
+ r);
return CUBEB_ERROR;
}
@@ -1540,20 +2436,21 @@ audiounit_configure_input(cubeb_stream * stm)
return CUBEB_ERROR;
}
- assert(stm->input_unit != NULL);
aurcbs_in.inputProc = audiounit_input_callback;
aurcbs_in.inputProcRefCon = stm;
- r = AudioUnitSetProperty(stm->input_unit,
- kAudioOutputUnitProperty_SetInputCallback,
- kAudioUnitScope_Global,
- AU_OUT_BUS,
- &aurcbs_in,
- sizeof(aurcbs_in));
+ r = AudioUnitSetProperty(
+ stm->input_unit, kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global, AU_OUT_BUS, &aurcbs_in, sizeof(aurcbs_in));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
+ LOG("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback "
+ "rv=%d",
+ r);
return CUBEB_ERROR;
}
+
+ stm->frames_read = 0;
+
LOG("(%p) Input audiounit init successfully.", stm);
return CUBEB_OK;
@@ -1562,12 +2459,14 @@ audiounit_configure_input(cubeb_stream * stm)
static int
audiounit_configure_output(cubeb_stream * stm)
{
+ assert(stm && stm->output_unit);
+
int r;
AURenderCallbackStruct aurcbs_out;
UInt32 size;
-
- LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
+ LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in "
+ "frames %u.",
stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
stm->output_stream_params.format, stm->latency_frames);
@@ -1581,62 +2480,75 @@ audiounit_configure_output(cubeb_stream * stm)
AudioStreamBasicDescription output_hw_desc;
size = sizeof(AudioStreamBasicDescription);
memset(&output_hw_desc, 0, size);
- r = AudioUnitGetProperty(stm->output_unit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output,
- AU_OUT_BUS,
- &output_hw_desc,
+ r = AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output, AU_OUT_BUS, &output_hw_desc,
&size);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
+ LOG("AudioUnitGetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
stm->output_hw_rate = output_hw_desc.mSampleRate;
- LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
+ LOG("(%p) Output device sampling rate: %.2f", stm,
+ output_hw_desc.mSampleRate);
+ stm->context->channels = output_hw_desc.mChannelsPerFrame;
+
+ // Set the input layout to match the output device layout.
+ audiounit_layout_init(stm, io_side::OUTPUT);
+ if (stm->context->channels != stm->output_stream_params.channels ||
+ stm->context->layout != stm->output_stream_params.layout) {
+ LOG("Incompatible channel layouts detected, setting up remixer");
+ audiounit_init_mixer(stm);
+ // We will be remixing the data before it reaches the output device.
+ // We need to adjust the number of channels and other
+ // AudioStreamDescription details.
+ stm->output_desc.mChannelsPerFrame = stm->context->channels;
+ stm->output_desc.mBytesPerFrame = (stm->output_desc.mBitsPerChannel / 8) *
+ stm->output_desc.mChannelsPerFrame;
+ stm->output_desc.mBytesPerPacket =
+ stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket;
+ } else {
+ stm->mixer = nullptr;
+ }
- r = AudioUnitSetProperty(stm->output_unit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
- AU_OUT_BUS,
- &stm->output_desc,
+ r = AudioUnitSetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, AU_OUT_BUS, &stm->output_desc,
sizeof(AudioStreamBasicDescription));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
- r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT);
+ r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::OUTPUT);
if (r != CUBEB_OK) {
LOG("(%p) Error in change output buffer size.", stm);
return CUBEB_ERROR;
}
/* Frames per buffer in the input callback. */
- r = AudioUnitSetProperty(stm->output_unit,
- kAudioUnitProperty_MaximumFramesPerSlice,
- kAudioUnitScope_Global,
- AU_OUT_BUS,
- &stm->latency_frames,
- sizeof(UInt32));
+ r = AudioUnitSetProperty(
+ stm->output_unit, kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Global, AU_OUT_BUS, &stm->latency_frames, sizeof(UInt32));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r);
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice "
+ "rv=%d",
+ r);
return CUBEB_ERROR;
}
- assert(stm->output_unit != NULL);
aurcbs_out.inputProc = audiounit_output_callback;
aurcbs_out.inputProcRefCon = stm;
- r = AudioUnitSetProperty(stm->output_unit,
- kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Global,
- AU_OUT_BUS,
- &aurcbs_out,
- sizeof(aurcbs_out));
+ r = AudioUnitSetProperty(
+ stm->output_unit, kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Global, AU_OUT_BUS, &aurcbs_out, sizeof(aurcbs_out));
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback "
+ "rv=%d",
+ r);
return CUBEB_ERROR;
}
+ stm->frames_written = 0;
+
LOG("(%p) Output audiounit init successfully.", stm);
return CUBEB_OK;
}
@@ -1646,11 +2558,37 @@ audiounit_setup_stream(cubeb_stream * stm)
{
stm->mutex.assert_current_thread_owns();
+ if ((stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) ||
+ (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK)) {
+ LOG("(%p) Loopback not supported for audiounit.", stm);
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
int r = 0;
+
+ device_info in_dev_info = stm->input_device;
+ device_info out_dev_info = stm->output_device;
+
+ if (has_input(stm) && has_output(stm) &&
+ stm->input_device.id != stm->output_device.id) {
+ r = audiounit_create_aggregate_device(stm);
+ if (r != CUBEB_OK) {
+ stm->aggregate_device_id = kAudioObjectUnknown;
+ LOG("(%p) Create aggregate devices failed.", stm);
+ // !!!NOTE: It is not necessary to return here. If it does not
+ // return it will fallback to the old implementation. The intention
+ // is to investigate how often it fails. I plan to remove
+ // it after a couple of weeks.
+ return r;
+ } else {
+ in_dev_info.id = out_dev_info.id = stm->aggregate_device_id;
+ in_dev_info.flags = DEV_INPUT;
+ out_dev_info.flags = DEV_OUTPUT;
+ }
+ }
+
if (has_input(stm)) {
- r = audiounit_create_unit(&stm->input_unit, true,
- &stm->input_stream_params,
- stm->input_device);
+ r = audiounit_create_unit(&stm->input_unit, &in_dev_info);
if (r != CUBEB_OK) {
LOG("(%p) AudioUnit creation for input failed.", stm);
return r;
@@ -1658,30 +2596,28 @@ audiounit_setup_stream(cubeb_stream * stm)
}
if (has_output(stm)) {
- r = audiounit_create_unit(&stm->output_unit, false,
- &stm->output_stream_params,
- stm->output_device);
+ r = audiounit_create_unit(&stm->output_unit, &out_dev_info);
if (r != CUBEB_OK) {
LOG("(%p) AudioUnit creation for output failed.", stm);
return r;
}
}
- /* Latency cannot change if another stream is operating in parallel. In this case
- * latecy is set to the other stream value. */
- if (stm->context->active_streams > 1) {
+ /* Latency cannot change if another stream is operating in parallel. In this
+ * case latency is set to the other stream value. */
+ if (audiounit_active_streams(stm->context) > 1) {
LOG("(%p) More than one active stream, use global latency.", stm);
stm->latency_frames = stm->context->global_latency_frames;
} else {
/* Silently clamp the latency down to the platform default, because we
- * synthetize the clock from the callbacks, and we want the clock to update
- * often. */
+ * synthetize the clock from the callbacks, and we want the clock to update
+ * often. */
stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames);
- assert(stm->latency_frames); // Ungly error check
- audiounit_set_global_latency(stm, stm->latency_frames);
+ assert(stm->latency_frames); // Ugly error check
+ audiounit_set_global_latency(stm->context, stm->latency_frames);
}
- /* Setup Input Stream! */
+ /* Configure I/O stream */
if (has_input(stm)) {
r = audiounit_configure_input(stm);
if (r != CUBEB_OK) {
@@ -1690,7 +2626,6 @@ audiounit_setup_stream(cubeb_stream * stm)
}
}
- /* Setup Output Stream! */
if (has_output(stm)) {
r = audiounit_configure_output(stm);
if (r != CUBEB_OK) {
@@ -1735,7 +2670,7 @@ audiounit_setup_stream(cubeb_stream * stm)
return CUBEB_ERROR;
}
}
-#else // TARGET_OS_IPHONE
+#else // TARGET_OS_IPHONE
//TODO: [[AVAudioSession sharedInstance] inputLatency]
// http://stackoverflow.com/questions/13157523/kaudiodevicepropertybufferframesize-replacement-for-ios
#endif
@@ -1762,13 +2697,10 @@ audiounit_setup_stream(cubeb_stream * stm)
/* Create resampler. Output params are unchanged
* because we do not need conversion on the output. */
- stm->resampler = cubeb_resampler_create(stm,
- has_input(stm) ? &input_unconverted_params : NULL,
- has_output(stm) ? &stm->output_stream_params : NULL,
- target_sample_rate,
- stm->data_callback,
- stm->user_ptr,
- CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ stm->resampler.reset(cubeb_resampler_create(
+ stm, has_input(stm) ? &input_unconverted_params : NULL,
+ has_output(stm) ? &stm->output_stream_params : NULL, target_sample_rate,
+ stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP));
if (!stm->resampler) {
LOG("(%p) Could not create resampler.", stm);
return CUBEB_ERROR;
@@ -1777,7 +2709,7 @@ audiounit_setup_stream(cubeb_stream * stm)
if (stm->input_unit != NULL) {
r = AudioUnitInitialize(stm->input_unit);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitInitialize/input", r);
+ LOG("AudioUnitInitialize/input rv=%d", r);
return CUBEB_ERROR;
}
}
@@ -1785,175 +2717,221 @@ audiounit_setup_stream(cubeb_stream * stm)
if (stm->output_unit != NULL) {
r = AudioUnitInitialize(stm->output_unit);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitInitialize/output", r);
+ LOG("AudioUnitInitialize/output rv=%d", r);
return CUBEB_ERROR;
}
+
+ stm->current_latency_frames = audiounit_get_device_presentation_latency(
+ stm->output_device.id, kAudioDevicePropertyScopeOutput);
+
+ Float64 unit_s;
+ UInt32 size = sizeof(unit_s);
+ if (AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_Latency,
+ kAudioUnitScope_Global, 0, &unit_s,
+ &size) == noErr) {
+ stm->current_latency_frames +=
+ static_cast<uint32_t>(unit_s * stm->output_desc.mSampleRate);
+ }
}
if (stm->input_unit && stm->output_unit) {
- // According to the I/O hardware rate it is expected a specific pattern of callbacks
- // for example is input is 44100 and output is 48000 we expected no more than 2
- // out callback in a row.
- stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate);
+ // According to the I/O hardware rate it is expected a specific pattern of
+ // callbacks for example is input is 44100 and output is 48000 we expected
+ // no more than 2 out callback in a row.
+ stm->expected_output_callbacks_in_a_row =
+ ceilf(stm->output_hw_rate / stm->input_hw_rate);
}
r = audiounit_install_device_changed_callback(stm);
if (r != CUBEB_OK) {
- LOG("(%p) Could not install the device change callback.", stm);
- return r;
+ LOG("(%p) Could not install all device change callback.", stm);
}
return CUBEB_OK;
}
+cubeb_stream::cubeb_stream(cubeb * context)
+ : context(context), resampler(nullptr, cubeb_resampler_destroy),
+ mixer(nullptr, cubeb_mixer_destroy)
+{
+ PodZero(&input_desc, 1);
+ PodZero(&output_desc, 1);
+}
+
+static void
+audiounit_stream_destroy_internal(cubeb_stream * stm);
+
static int
-audiounit_stream_init(cubeb * context,
- cubeb_stream ** stream,
- char const * /* stream_name */,
- cubeb_devid input_device,
+audiounit_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * /* stream_name */, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
unsigned int latency_frames,
cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
+ cubeb_state_callback state_callback, void * user_ptr)
{
- cubeb_stream * stm;
- int r;
-
assert(context);
+ auto_lock context_lock(context->mutex);
+ audiounit_increment_active_streams(context);
+ unique_ptr<cubeb_stream, decltype(&audiounit_stream_destroy)> stm(
+ new cubeb_stream(context), audiounit_stream_destroy_internal);
+ int r;
*stream = NULL;
-
assert(latency_frames > 0);
- if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) {
- LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX);
- return CUBEB_ERROR;
- }
-
- stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream));
- assert(stm);
- // Placement new to call the ctors of cubeb_stream members.
- new (stm) cubeb_stream();
/* These could be different in the future if we have both
* full-duplex stream and different devices for input vs output. */
- stm->context = context;
stm->data_callback = data_callback;
stm->state_callback = state_callback;
stm->user_ptr = user_ptr;
stm->latency_frames = latency_frames;
- stm->device_changed_callback = NULL;
+
+ if ((input_device && !input_stream_params) ||
+ (output_device && !output_stream_params)) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
if (input_stream_params) {
stm->input_stream_params = *input_stream_params;
- stm->input_device = input_device;
- stm->is_default_input = input_device == nullptr ||
- (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) ==
- reinterpret_cast<intptr_t>(input_device));
+ r = audiounit_set_device_info(
+ stm.get(), reinterpret_cast<uintptr_t>(input_device), io_side::INPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Fail to set device info for input.", stm.get());
+ return r;
+ }
}
if (output_stream_params) {
stm->output_stream_params = *output_stream_params;
- stm->output_device = output_device;
+ r = audiounit_set_device_info(
+ stm.get(), reinterpret_cast<uintptr_t>(output_device), io_side::OUTPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Fail to set device info for output.", stm.get());
+ return r;
+ }
}
- /* Init data members where necessary */
- stm->hw_latency_frames = UINT64_MAX;
-
- stm->switching_device = false;
-
- auto_lock context_lock(context->mutex);
{
// It's not critical to lock here, because no other thread has been started
// yet, but it allows to assert that the lock has been taken in
// `audiounit_setup_stream`.
- context->active_streams += 1;
auto_lock lock(stm->mutex);
- r = audiounit_setup_stream(stm);
+ r = audiounit_setup_stream(stm.get());
}
if (r != CUBEB_OK) {
- LOG("(%p) Could not setup the audiounit stream.", stm);
- audiounit_stream_destroy(stm);
+ LOG("(%p) Could not setup the audiounit stream.", stm.get());
return r;
}
- r = audiounit_install_system_changed_callback(stm);
+ r = audiounit_install_system_changed_callback(stm.get());
if (r != CUBEB_OK) {
- LOG("(%p) Could not install the device change callback.", stm);
+ LOG("(%p) Could not install the device change callback.", stm.get());
return r;
}
- *stream = stm;
- LOG("Cubeb stream (%p) init successful.", stm);
+ *stream = stm.release();
+ LOG("(%p) Cubeb stream init successful.", *stream);
return CUBEB_OK;
}
static void
-audiounit_close_stream(cubeb_stream *stm)
+audiounit_close_stream(cubeb_stream * stm)
{
stm->mutex.assert_current_thread_owns();
if (stm->input_unit) {
AudioUnitUninitialize(stm->input_unit);
AudioComponentInstanceDispose(stm->input_unit);
+ stm->input_unit = nullptr;
}
- audiounit_destroy_input_linear_buffer(stm);
+ stm->input_linear_buffer.reset();
if (stm->output_unit) {
AudioUnitUninitialize(stm->output_unit);
AudioComponentInstanceDispose(stm->output_unit);
+ stm->output_unit = nullptr;
}
- cubeb_resampler_destroy(stm->resampler);
+ stm->resampler.reset();
+ stm->mixer.reset();
+
+ if (stm->aggregate_device_id != kAudioObjectUnknown) {
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ stm->aggregate_device_id = kAudioObjectUnknown;
+ }
}
static void
-audiounit_stream_destroy(cubeb_stream * stm)
+audiounit_stream_destroy_internal(cubeb_stream * stm)
{
- stm->shutdown = true;
+ stm->context->mutex.assert_current_thread_owns();
int r = audiounit_uninstall_system_changed_callback(stm);
if (r != CUBEB_OK) {
LOG("(%p) Could not uninstall the device changed callback", stm);
}
-
r = audiounit_uninstall_device_changed_callback(stm);
if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall all device change listeners", stm);
+ }
+
+ auto_lock lock(stm->mutex);
+ audiounit_close_stream(stm);
+ assert(audiounit_active_streams(stm->context) >= 1);
+ audiounit_decrement_active_streams(stm->context);
+}
+
+static void
+audiounit_stream_destroy(cubeb_stream * stm)
+{
+ int r = audiounit_uninstall_system_changed_callback(stm);
+ if (r != CUBEB_OK) {
LOG("(%p) Could not uninstall the device changed callback", stm);
}
+ r = audiounit_uninstall_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall all device change listeners", stm);
+ }
- auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
+ if (!stm->shutdown.load()) {
+ auto_lock context_lock(stm->context->mutex);
+ audiounit_stream_stop_internal(stm);
+ stm->shutdown = true;
+ }
+ stm->destroy_pending = true;
// Execute close in serial queue to avoid collision
// with reinit when un/plug devices
dispatch_sync(stm->context->serial_queue, ^() {
- auto_lock lock(stm->mutex);
- audiounit_close_stream(stm);
+ auto_lock context_lock(stm->context->mutex);
+ audiounit_stream_destroy_internal(stm);
});
- assert(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
-
LOG("Cubeb stream (%p) destroyed successful.", stm);
-
- stm->~cubeb_stream();
- free(stm);
+ delete stm;
}
-void
+static int
audiounit_stream_start_internal(cubeb_stream * stm)
{
OSStatus r;
if (stm->input_unit != NULL) {
r = AudioOutputUnitStart(stm->input_unit);
- assert(r == 0);
+ if (r != noErr) {
+ LOG("AudioOutputUnitStart (input) rv=%d", r);
+ return CUBEB_ERROR;
+ }
}
if (stm->output_unit != NULL) {
r = AudioOutputUnitStart(stm->output_unit);
- assert(r == 0);
+ if (r != noErr) {
+ LOG("AudioOutputUnitStart (output) rv=%d", r);
+ return CUBEB_ERROR;
+ }
}
+ return CUBEB_OK;
}
static int
@@ -1963,7 +2941,10 @@ audiounit_stream_start(cubeb_stream * stm)
stm->shutdown = false;
stm->draining = false;
- audiounit_stream_start_internal(stm);
+ int r = audiounit_stream_start_internal(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
@@ -2002,9 +2983,12 @@ audiounit_stream_stop(cubeb_stream * stm)
static int
audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position)
{
- auto_lock lock(stm->mutex);
-
- *position = stm->frames_played;
+ assert(stm);
+ if (stm->current_latency_frames > stm->frames_played) {
+ *position = 0;
+ } else {
+ *position = stm->frames_played - stm->current_latency_frames;
+ }
return CUBEB_OK;
}
@@ -2012,77 +2996,10 @@ int
audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
#if TARGET_OS_IPHONE
- //TODO
+ // TODO
return CUBEB_ERROR_NOT_SUPPORTED;
#else
- auto_lock lock(stm->mutex);
- if (stm->hw_latency_frames == UINT64_MAX) {
- UInt32 size;
- uint32_t device_latency_frames, device_safety_offset;
- double unit_latency_sec;
- AudioDeviceID output_device_id;
- OSStatus r;
- AudioObjectPropertyAddress latency_address = {
- kAudioDevicePropertyLatency,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
- AudioObjectPropertyAddress safety_offset_address = {
- kAudioDevicePropertySafetyOffset,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
-
- r = audiounit_get_output_device_id(&output_device_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- size = sizeof(unit_latency_sec);
- r = AudioUnitGetProperty(stm->output_unit,
- kAudioUnitProperty_Latency,
- kAudioUnitScope_Global,
- 0,
- &unit_latency_sec,
- &size);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetProperty/kAudioUnitProperty_Latency", r);
- return CUBEB_ERROR;
- }
-
- size = sizeof(device_latency_frames);
- r = AudioObjectGetPropertyData(output_device_id,
- &latency_address,
- 0,
- NULL,
- &size,
- &device_latency_frames);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetPropertyData/latency_frames", r);
- return CUBEB_ERROR;
- }
-
- size = sizeof(device_safety_offset);
- r = AudioObjectGetPropertyData(output_device_id,
- &safety_offset_address,
- 0,
- NULL,
- &size,
- &device_safety_offset);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitGetPropertyData/safety_offset", r);
- return CUBEB_ERROR;
- }
-
- /* This part is fixed and depend on the stream parameter and the hardware. */
- stm->hw_latency_frames =
- static_cast<uint32_t>(unit_latency_sec * stm->output_desc.mSampleRate)
- + device_latency_frames
- + device_safety_offset;
- }
-
- *latency = stm->hw_latency_frames + stm->current_latency_frames;
-
+ *latency = stm->total_output_latency_frames;
return CUBEB_OK;
#endif
}
@@ -2091,10 +3008,8 @@ static int
audiounit_stream_get_volume(cubeb_stream * stm, float * volume)
{
assert(stm->output_unit);
- OSStatus r = AudioUnitGetParameter(stm->output_unit,
- kHALOutputParam_Volume,
- kAudioUnitScope_Global,
- 0, volume);
+ OSStatus r = AudioUnitGetParameter(stm->output_unit, kHALOutputParam_Volume,
+ kAudioUnitScope_Global, 0, volume);
if (r != noErr) {
LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r);
return CUBEB_ERROR;
@@ -2102,182 +3017,137 @@ audiounit_stream_get_volume(cubeb_stream * stm, float * volume)
return CUBEB_OK;
}
-int audiounit_stream_set_volume(cubeb_stream * stm, float volume)
+static int
+audiounit_stream_set_volume(cubeb_stream * stm, float volume)
{
+ assert(stm->output_unit);
OSStatus r;
-
- r = AudioUnitSetParameter(stm->output_unit,
- kHALOutputParam_Volume,
- kAudioUnitScope_Global,
- 0, volume, 0);
+ r = AudioUnitSetParameter(stm->output_unit, kHALOutputParam_Volume,
+ kAudioUnitScope_Global, 0, volume, 0);
if (r != noErr) {
- PRINT_ERROR_CODE("AudioUnitSetParameter/kHALOutputParam_Volume", r);
+ LOG("AudioUnitSetParameter/kHALOutputParam_Volume rv=%d", r);
return CUBEB_ERROR;
}
return CUBEB_OK;
}
-int audiounit_stream_set_panning(cubeb_stream * stm, float panning)
+unique_ptr<char[]>
+convert_uint32_into_string(UInt32 data)
{
- if (stm->output_desc.mChannelsPerFrame > 2) {
- return CUBEB_ERROR_INVALID_PARAMETER;
+ // Simply create an empty string if no data.
+ size_t size = data == 0 ? 0 : 4; // 4 bytes for uint32.
+ auto str = unique_ptr<char[]>{new char[size + 1]}; // + 1 for '\0'.
+ str[size] = '\0';
+ if (size < 4) {
+ return str;
}
- stm->panning.store(panning, std::memory_order_relaxed);
- return CUBEB_OK;
+ // Reverse 0xWXYZ into 0xZYXW.
+ str[0] = (char)(data >> 24);
+ str[1] = (char)(data >> 16);
+ str[2] = (char)(data >> 8);
+ str[3] = (char)(data);
+ return str;
}
-int audiounit_stream_get_current_device(cubeb_stream * stm,
- cubeb_device ** const device)
+int
+audiounit_get_default_device_datasource(cubeb_device_type type, UInt32 * data)
{
-#if TARGET_OS_IPHONE
- //TODO
- return CUBEB_ERROR_NOT_SUPPORTED;
-#else
- OSStatus r;
- UInt32 size;
- UInt32 data;
- char strdata[4];
- AudioDeviceID output_device_id;
- AudioDeviceID input_device_id;
-
- AudioObjectPropertyAddress datasource_address = {
- kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeOutput,
- kAudioObjectPropertyElementMaster
- };
-
- AudioObjectPropertyAddress datasource_address_input = {
- kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeInput,
- kAudioObjectPropertyElementMaster
- };
-
- *device = NULL;
-
- if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+ AudioDeviceID id = audiounit_get_default_device_id(type);
+ if (id == kAudioObjectUnknown) {
return CUBEB_ERROR;
}
- *device = new cubeb_device;
- if (!*device) {
- return CUBEB_ERROR;
- }
- PodZero(*device, 1);
-
- size = sizeof(UInt32);
- /* This fails with some USB headset, so simply return an empty string. */
- r = AudioObjectGetPropertyData(output_device_id,
- &datasource_address,
- 0, NULL, &size, &data);
+ UInt32 size = sizeof(*data);
+ /* This fails with some USB headsets (e.g., Plantronic .Audio 628). */
+ OSStatus r = AudioObjectGetPropertyData(
+ id,
+ type == CUBEB_DEVICE_TYPE_INPUT ? &INPUT_DATA_SOURCE_PROPERTY_ADDRESS
+ : &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ 0, NULL, &size, data);
if (r != noErr) {
- size = 0;
- data = 0;
- }
-
- (*device)->output_name = new char[size + 1];
- if (!(*device)->output_name) {
- return CUBEB_ERROR;
+ *data = 0;
}
- // Turn the four chars packed into a uint32 into a string
- strdata[0] = (char)(data >> 24);
- strdata[1] = (char)(data >> 16);
- strdata[2] = (char)(data >> 8);
- strdata[3] = (char)(data);
+ return CUBEB_OK;
+}
- memcpy((*device)->output_name, strdata, size);
- (*device)->output_name[size] = '\0';
+int
+audiounit_get_default_device_name(cubeb_stream * stm,
+ cubeb_device * const device,
+ cubeb_device_type type)
+{
+ assert(stm);
+ assert(device);
- if (audiounit_get_input_device_id(&input_device_id) != CUBEB_OK) {
- return CUBEB_ERROR;
+ UInt32 data;
+ int r = audiounit_get_default_device_datasource(type, &data);
+ if (r != CUBEB_OK) {
+ return r;
}
-
- size = sizeof(UInt32);
- r = AudioObjectGetPropertyData(input_device_id, &datasource_address_input, 0, NULL, &size, &data);
- if (r != noErr) {
- LOG("(%p) Error when getting device !", stm);
- size = 0;
- data = 0;
+ char ** name = type == CUBEB_DEVICE_TYPE_INPUT ? &device->input_name
+ : &device->output_name;
+ *name = convert_uint32_into_string(data).release();
+ if (!strlen(*name)) { // empty string.
+ LOG("(%p) name of %s device is empty!", stm,
+ type == CUBEB_DEVICE_TYPE_INPUT ? "input" : "output");
}
+ return CUBEB_OK;
+}
- (*device)->input_name = new char[size + 1];
- if (!(*device)->input_name) {
+int
+audiounit_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device)
+{
+#if TARGET_OS_IPHONE
+ // TODO
+ return CUBEB_ERROR_NOT_SUPPORTED;
+#else
+ *device = new cubeb_device;
+ if (!*device) {
return CUBEB_ERROR;
}
+ PodZero(*device, 1);
- // Turn the four chars packed into a uint32 into a string
- strdata[0] = (char)(data >> 24);
- strdata[1] = (char)(data >> 16);
- strdata[2] = (char)(data >> 8);
- strdata[3] = (char)(data);
+ int r =
+ audiounit_get_default_device_name(stm, *device, CUBEB_DEVICE_TYPE_OUTPUT);
+ if (r != CUBEB_OK) {
+ return r;
+ }
- memcpy((*device)->input_name, strdata, size);
- (*device)->input_name[size] = '\0';
+ r = audiounit_get_default_device_name(stm, *device, CUBEB_DEVICE_TYPE_INPUT);
+ if (r != CUBEB_OK) {
+ return r;
+ }
return CUBEB_OK;
#endif
}
-int audiounit_stream_device_destroy(cubeb_stream * /* stream */,
- cubeb_device * device)
+int
+audiounit_stream_device_destroy(cubeb_stream * /* stream */,
+ cubeb_device * device)
{
- delete [] device->output_name;
- delete [] device->input_name;
+ delete[] device->output_name;
+ delete[] device->input_name;
delete device;
return CUBEB_OK;
}
-int audiounit_stream_register_device_changed_callback(cubeb_stream * stream,
- cubeb_device_changed_callback device_changed_callback)
+int
+audiounit_stream_register_device_changed_callback(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback)
{
+ auto_lock dev_cb_lock(stream->device_changed_callback_lock);
/* Note: second register without unregister first causes 'nope' error.
* Current implementation requires unregister before register a new cb. */
- assert(!stream->device_changed_callback);
-
- auto_lock lock(stream->mutex);
-
+ assert(!device_changed_callback || !stream->device_changed_callback);
stream->device_changed_callback = device_changed_callback;
-
return CUBEB_OK;
}
-static OSStatus
-audiounit_get_devices(AudioObjectID ** devices, uint32_t * count)
-{
- OSStatus ret;
- UInt32 size = 0;
- AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster };
-
- ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size);
- if (ret != noErr) {
- return ret;
- }
-
- *count = static_cast<uint32_t>(size / sizeof(AudioObjectID));
- if (size >= sizeof(AudioObjectID)) {
- if (*devices != NULL) {
- delete [] (*devices);
- }
- *devices = new AudioObjectID[*count];
- PodZero(*devices, *count);
-
- ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, (void *)*devices);
- if (ret != noErr) {
- delete [] (*devices);
- *devices = NULL;
- }
- } else {
- *devices = NULL;
- ret = -1;
- }
-
- return ret;
-}
-
static char *
audiounit_strref_to_cstr_utf8(CFStringRef strref)
{
@@ -2288,11 +3158,12 @@ audiounit_strref_to_cstr_utf8(CFStringRef strref)
}
len = CFStringGetLength(strref);
- size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8);
- ret = static_cast<char *>(malloc(size));
+ // Add 1 to size to allow for '\0' termination character.
+ size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+ ret = new char[size];
if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) {
- free(ret);
+ delete[] ret;
ret = NULL;
}
@@ -2302,15 +3173,18 @@ audiounit_strref_to_cstr_utf8(CFStringRef strref)
static uint32_t
audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope)
{
- AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
+ AudioObjectPropertyAddress adr = {0, scope,
+ kAudioObjectPropertyElementMaster};
UInt32 size = 0;
uint32_t i, ret = 0;
adr.mSelector = kAudioDevicePropertyStreamConfiguration;
- if (AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr && size > 0) {
+ if (AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr &&
+ size > 0) {
AudioBufferList * list = static_cast<AudioBufferList *>(alloca(size));
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) == noErr) {
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) ==
+ noErr) {
for (i = 0; i < list->mNumberBuffers; i++)
ret += list->mBuffers[i].mNumberChannels;
}
@@ -2320,16 +3194,20 @@ audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope)
}
static void
-audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope,
- uint32_t * min, uint32_t * max, uint32_t * def)
+audiounit_get_available_samplerate(AudioObjectID devid,
+ AudioObjectPropertyScope scope,
+ uint32_t * min, uint32_t * max,
+ uint32_t * def)
{
- AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
+ AudioObjectPropertyAddress adr = {0, scope,
+ kAudioObjectPropertyElementMaster};
adr.mSelector = kAudioDevicePropertyNominalSampleRate;
if (AudioObjectHasProperty(devid, &adr)) {
UInt32 size = sizeof(Float64);
Float64 fvalue = 0.0;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &fvalue) == noErr) {
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &fvalue) ==
+ noErr) {
*def = fvalue;
}
}
@@ -2339,32 +3217,33 @@ audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope
AudioValueRange range;
if (AudioObjectHasProperty(devid, &adr) &&
AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr) {
- uint32_t i, count = size / sizeof(AudioValueRange);
- AudioValueRange * ranges = new AudioValueRange[count];
+ uint32_t count = size / sizeof(AudioValueRange);
+ vector<AudioValueRange> ranges(count);
range.mMinimum = 9999999999.0;
range.mMaximum = 0.0;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges) == noErr) {
- for (i = 0; i < count; i++) {
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size,
+ ranges.data()) == noErr) {
+ for (uint32_t i = 0; i < count; i++) {
if (ranges[i].mMaximum > range.mMaximum)
range.mMaximum = ranges[i].mMaximum;
if (ranges[i].mMinimum < range.mMinimum)
range.mMinimum = ranges[i].mMinimum;
}
}
- delete [] ranges;
*max = static_cast<uint32_t>(range.mMaximum);
*min = static_cast<uint32_t>(range.mMinimum);
} else {
*min = *max = 0;
}
-
}
static UInt32
-audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope)
+audiounit_get_device_presentation_latency(AudioObjectID devid,
+ AudioObjectPropertyScope scope)
{
- AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
- UInt32 size, dev, stream = 0, offset;
+ AudioObjectPropertyAddress adr = {0, scope,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size, dev, stream = 0;
AudioStreamID sid[1];
adr.mSelector = kAudioDevicePropertyLatency;
@@ -2381,354 +3260,411 @@ audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectProper
AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream);
}
- adr.mSelector = kAudioDevicePropertySafetyOffset;
- size = sizeof(UInt32);
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &offset) != noErr) {
- offset = 0;
- }
-
- return dev + stream + offset;
+ return dev + stream;
}
-static cubeb_device_info *
-audiounit_create_device_from_hwdev(AudioObjectID devid, cubeb_device_type type)
+static int
+audiounit_create_device_from_hwdev(cubeb_device_info * dev_info,
+ AudioObjectID devid, cubeb_device_type type)
{
- AudioObjectPropertyAddress adr = { 0, 0, kAudioObjectPropertyElementMaster };
- UInt32 size, ch, latency;
- cubeb_device_info * ret;
- CFStringRef str = NULL;
- AudioValueRange range;
+ AudioObjectPropertyAddress adr = {0, 0, kAudioObjectPropertyElementMaster};
+ UInt32 size;
if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
adr.mScope = kAudioDevicePropertyScopeOutput;
} else if (type == CUBEB_DEVICE_TYPE_INPUT) {
adr.mScope = kAudioDevicePropertyScopeInput;
} else {
- return NULL;
+ return CUBEB_ERROR;
}
- ch = audiounit_get_channel_count(devid, adr.mScope);
+ UInt32 ch = audiounit_get_channel_count(devid, adr.mScope);
if (ch == 0) {
- return NULL;
+ return CUBEB_ERROR;
}
- ret = new cubeb_device_info;
- PodZero(ret, 1);
+ PodZero(dev_info, 1);
+ CFStringRef device_id_str = nullptr;
size = sizeof(CFStringRef);
adr.mSelector = kAudioDevicePropertyDeviceUID;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) {
- ret->device_id = audiounit_strref_to_cstr_utf8(str);
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->group_id = strdup(ret->device_id);
- CFRelease(str);
+ OSStatus ret =
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &device_id_str);
+ if (ret == noErr && device_id_str != NULL) {
+ dev_info->device_id = audiounit_strref_to_cstr_utf8(device_id_str);
+ static_assert(sizeof(cubeb_devid) >= sizeof(decltype(devid)),
+ "cubeb_devid can't represent devid");
+ dev_info->devid = reinterpret_cast<cubeb_devid>(devid);
+ dev_info->group_id = dev_info->device_id;
+ CFRelease(device_id_str);
+ }
+
+ CFStringRef friendly_name_str = nullptr;
+ UInt32 ds;
+ size = sizeof(UInt32);
+ adr.mSelector = kAudioDevicePropertyDataSource;
+ ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds);
+ if (ret == noErr) {
+ AudioValueTranslation trl = {&ds, sizeof(ds), &friendly_name_str,
+ sizeof(CFStringRef)};
+ adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString;
+ size = sizeof(AudioValueTranslation);
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl);
}
- size = sizeof(CFStringRef);
- adr.mSelector = kAudioObjectPropertyName;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) {
- UInt32 ds;
- size = sizeof(UInt32);
- adr.mSelector = kAudioDevicePropertyDataSource;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds) == noErr) {
- CFStringRef dsname;
- AudioValueTranslation trl = { &ds, sizeof(ds), &dsname, sizeof(dsname) };
- adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString;
- size = sizeof(AudioValueTranslation);
- // If there is a datasource for this device, use it instead of the device
- // name.
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl) == noErr) {
- CFRelease(str);
- str = dsname;
- }
- }
+ // If there is no datasource for this device, fall back to the
+ // device name.
+ if (!friendly_name_str) {
+ size = sizeof(CFStringRef);
+ adr.mSelector = kAudioObjectPropertyName;
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &friendly_name_str);
+ }
- ret->friendly_name = audiounit_strref_to_cstr_utf8(str);
- CFRelease(str);
+ if (friendly_name_str) {
+ dev_info->friendly_name = audiounit_strref_to_cstr_utf8(friendly_name_str);
+ CFRelease(friendly_name_str);
+ } else {
+ // Couldn't get a datasource name nor a device name, return a
+ // valid string of length 0.
+ char * fallback_name = new char[1];
+ fallback_name[0] = '\0';
+ dev_info->friendly_name = fallback_name;
}
+ CFStringRef vendor_name_str = nullptr;
size = sizeof(CFStringRef);
adr.mSelector = kAudioObjectPropertyManufacturer;
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) {
- ret->vendor_name = audiounit_strref_to_cstr_utf8(str);
- CFRelease(str);
- }
-
- ret->type = type;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = (devid == audiounit_get_default_device_id(type)) ?
- CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
-
- ret->max_channels = ch;
- ret->format = (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */
+ ret =
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &vendor_name_str);
+ if (ret == noErr && vendor_name_str != NULL) {
+ dev_info->vendor_name = audiounit_strref_to_cstr_utf8(vendor_name_str);
+ CFRelease(vendor_name_str);
+ }
+
+ dev_info->type = type;
+ dev_info->state = CUBEB_DEVICE_STATE_ENABLED;
+ dev_info->preferred = (devid == audiounit_get_default_device_id(type))
+ ? CUBEB_DEVICE_PREF_ALL
+ : CUBEB_DEVICE_PREF_NONE;
+
+ dev_info->max_channels = ch;
+ dev_info->format =
+ (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */
/* kAudioFormatFlagsAudioUnitCanonical is deprecated, prefer floating point */
- ret->default_format = CUBEB_DEVICE_FMT_F32NE;
- audiounit_get_available_samplerate(devid, adr.mScope,
- &ret->min_rate, &ret->max_rate, &ret->default_rate);
+ dev_info->default_format = CUBEB_DEVICE_FMT_F32NE;
+ audiounit_get_available_samplerate(devid, adr.mScope, &dev_info->min_rate,
+ &dev_info->max_rate,
+ &dev_info->default_rate);
- latency = audiounit_get_device_presentation_latency(devid, adr.mScope);
+ UInt32 latency = audiounit_get_device_presentation_latency(devid, adr.mScope);
+ AudioValueRange range;
adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange;
size = sizeof(AudioValueRange);
- if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range) == noErr) {
- ret->latency_lo = latency + range.mMinimum;
- ret->latency_hi = latency + range.mMaximum;
+ ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range);
+ if (ret == noErr) {
+ dev_info->latency_lo = latency + range.mMinimum;
+ dev_info->latency_hi = latency + range.mMaximum;
} else {
- ret->latency_lo = 10 * ret->default_rate / 1000; /* Default to 10ms */
- ret->latency_hi = 100 * ret->default_rate / 1000; /* Default to 100ms */
+ dev_info->latency_lo =
+ 10 * dev_info->default_rate / 1000; /* Default to 10ms */
+ dev_info->latency_hi =
+ 100 * dev_info->default_rate / 1000; /* Default to 100ms */
}
- return ret;
+ return CUBEB_OK;
+}
+
+bool
+is_aggregate_device(cubeb_device_info * device_info)
+{
+ assert(device_info->friendly_name);
+ return !strncmp(device_info->friendly_name, PRIVATE_AGGREGATE_DEVICE_NAME,
+ strlen(PRIVATE_AGGREGATE_DEVICE_NAME));
}
static int
audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type,
- cubeb_device_collection ** collection)
+ cubeb_device_collection * collection)
{
- AudioObjectID * hwdevs = NULL;
- uint32_t i, hwdevcount = 0;
- OSStatus err;
+ vector<AudioObjectID> input_devs;
+ vector<AudioObjectID> output_devs;
- if ((err = audiounit_get_devices(&hwdevs, &hwdevcount)) != noErr) {
- return CUBEB_ERROR;
+ // Count number of input and output devices. This is not
+ // necessarily the same as the count of raw devices supported by the
+ // system since, for example, with Soundflower installed, some
+ // devices may report as being both input *and* output and cubeb
+ // separates those into two different devices.
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ output_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
}
- *collection = static_cast<cubeb_device_collection *>(malloc(sizeof(cubeb_device_collection) +
- sizeof(cubeb_device_info*) * (hwdevcount > 0 ? hwdevcount - 1 : 0)));
- (*collection)->count = 0;
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ input_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ }
- if (hwdevcount > 0) {
- cubeb_device_info * cur;
+ auto devices = new cubeb_device_info[output_devs.size() + input_devs.size()];
+ collection->count = 0;
- if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
- for (i = 0; i < hwdevcount; i++) {
- if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_OUTPUT)) != NULL)
- (*collection)->device[(*collection)->count++] = cur;
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ for (auto dev : output_devs) {
+ auto device = &devices[collection->count];
+ auto err = audiounit_create_device_from_hwdev(device, dev,
+ CUBEB_DEVICE_TYPE_OUTPUT);
+ if (err != CUBEB_OK || is_aggregate_device(device)) {
+ continue;
}
+ collection->count += 1;
}
+ }
- if (type & CUBEB_DEVICE_TYPE_INPUT) {
- for (i = 0; i < hwdevcount; i++) {
- if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_INPUT)) != NULL)
- (*collection)->device[(*collection)->count++] = cur;
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ for (auto dev : input_devs) {
+ auto device = &devices[collection->count];
+ auto err = audiounit_create_device_from_hwdev(device, dev,
+ CUBEB_DEVICE_TYPE_INPUT);
+ if (err != CUBEB_OK || is_aggregate_device(device)) {
+ continue;
}
+ collection->count += 1;
}
}
- delete [] hwdevs;
+ if (collection->count > 0) {
+ collection->device = devices;
+ } else {
+ delete[] devices;
+ collection->device = NULL;
+ }
return CUBEB_OK;
}
-/* qsort compare method. */
-int compare_devid(const void * a, const void * b)
+static void
+audiounit_device_destroy(cubeb_device_info * device)
{
- return (*(AudioObjectID*)a - *(AudioObjectID*)b);
+ delete[] device->device_id;
+ delete[] device->friendly_name;
+ delete[] device->vendor_name;
}
-static uint32_t
-audiounit_get_devices_of_type(cubeb_device_type devtype, AudioObjectID ** devid_array)
+static int
+audiounit_device_collection_destroy(cubeb * /* context */,
+ cubeb_device_collection * collection)
{
- assert(devid_array == NULL || *devid_array == NULL);
+ for (size_t i = 0; i < collection->count; i++) {
+ audiounit_device_destroy(&collection->device[i]);
+ }
+ delete[] collection->device;
- AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster };
+ return CUBEB_OK;
+}
+
+static vector<AudioObjectID>
+audiounit_get_devices_of_type(cubeb_device_type devtype)
+{
UInt32 size = 0;
- OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size);
+ OSStatus ret = AudioObjectGetPropertyDataSize(
+ kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size);
if (ret != noErr) {
- return 0;
+ return vector<AudioObjectID>();
}
- /* Total number of input and output devices. */
- uint32_t count = (uint32_t)(size / sizeof(AudioObjectID));
-
- AudioObjectID devices[count];
- ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devices);
+ vector<AudioObjectID> devices(size / sizeof(AudioObjectID));
+ ret = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size,
+ devices.data());
if (ret != noErr) {
- return 0;
+ return vector<AudioObjectID>();
}
+
+ // Remove the aggregate device from the list of devices (if any).
+ for (auto it = devices.begin(); it != devices.end();) {
+ CFStringRef name = get_device_name(*it);
+ if (name && CFStringFind(name, CFSTR("CubebAggregateDevice"), 0).location !=
+ kCFNotFound) {
+ it = devices.erase(it);
+ } else {
+ it++;
+ }
+ if (name) {
+ CFRelease(name);
+ }
+ }
+
/* Expected sorted but did not find anything in the docs. */
- qsort(devices, count, sizeof(AudioObjectID), compare_devid);
+ sort(devices.begin(), devices.end(),
+ [](AudioObjectID a, AudioObjectID b) { return a < b; });
if (devtype == (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) {
- if (devid_array) {
- *devid_array = new AudioObjectID[count];
- assert(*devid_array);
- memcpy(*devid_array, &devices, count * sizeof(AudioObjectID));
- }
- return count;
+ return devices;
}
- AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT) ?
- kAudioDevicePropertyScopeInput :
- kAudioDevicePropertyScopeOutput;
+ AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT)
+ ? kAudioDevicePropertyScopeInput
+ : kAudioDevicePropertyScopeOutput;
- uint32_t dev_count = 0;
- AudioObjectID devices_in_scope[count];
- for(uint32_t i = 0; i < count; ++i) {
+ vector<AudioObjectID> devices_in_scope;
+ for (uint32_t i = 0; i < devices.size(); ++i) {
/* For device in the given scope channel must be > 0. */
if (audiounit_get_channel_count(devices[i], scope) > 0) {
- devices_in_scope[dev_count] = devices[i];
- ++dev_count;
+ devices_in_scope.push_back(devices[i]);
}
}
- if (devid_array && dev_count > 0) {
- *devid_array = new AudioObjectID[dev_count];
- assert(*devid_array);
- memcpy(*devid_array, &devices_in_scope, dev_count * sizeof(AudioObjectID));
- }
- return dev_count;
-}
-
-static uint32_t
-audiounit_equal_arrays(AudioObjectID * left, AudioObjectID * right, uint32_t size)
-{
- /* Expected sorted arrays. */
- for (uint32_t i = 0; i < size; ++i) {
- if (left[i] != right[i]) {
- return 0;
- }
- }
- return 1;
+ return devices_in_scope;
}
static OSStatus
-audiounit_collection_changed_callback(AudioObjectID /* inObjectID */,
- UInt32 /* inNumberAddresses */,
- const AudioObjectPropertyAddress * /* inAddresses */,
- void * inClientData)
+audiounit_collection_changed_callback(
+ AudioObjectID /* inObjectID */, UInt32 /* inNumberAddresses */,
+ const AudioObjectPropertyAddress * /* inAddresses */, void * inClientData)
{
cubeb * context = static_cast<cubeb *>(inClientData);
- auto_lock lock(context->mutex);
- if (context->collection_changed_callback == NULL) {
- /* Listener removed while waiting in mutex, abort. */
- return noErr;
- }
-
- /* Differentiate input from output changes. */
- if (context->collection_changed_devtype == CUBEB_DEVICE_TYPE_INPUT ||
- context->collection_changed_devtype == CUBEB_DEVICE_TYPE_OUTPUT) {
- AudioObjectID * devices = NULL;
- uint32_t new_number_of_devices = audiounit_get_devices_of_type(context->collection_changed_devtype, &devices);
- /* When count is the same examine the devid for the case of coalescing. */
- if (context->devtype_device_count == new_number_of_devices &&
- audiounit_equal_arrays(devices, context->devtype_device_array, new_number_of_devices)) {
- /* Device changed for the other scope, ignore. */
- delete [] devices;
- return noErr;
+ // This can be called from inside an AudioUnit function, dispatch to another
+ // queue.
+ dispatch_async(context->serial_queue, ^() {
+ auto_lock lock(context->mutex);
+ if (!context->input_collection_changed_callback &&
+ !context->output_collection_changed_callback) {
+ /* Listener removed while waiting in mutex, abort. */
+ return;
}
- /* Device on desired scope changed, reset counter and array. */
- context->devtype_device_count = new_number_of_devices;
- /* Free the old array before replace. */
- delete [] context->devtype_device_array;
- context->devtype_device_array = devices;
- }
-
- context->collection_changed_callback(context, context->collection_changed_user_ptr);
+ if (context->input_collection_changed_callback) {
+ vector<AudioObjectID> devices =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ /* Elements in the vector expected sorted. */
+ if (context->input_device_array != devices) {
+ context->input_device_array = devices;
+ context->input_collection_changed_callback(
+ context, context->input_collection_changed_user_ptr);
+ }
+ }
+ if (context->output_collection_changed_callback) {
+ vector<AudioObjectID> devices =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+ /* Elements in the vector expected sorted. */
+ if (context->output_device_array != devices) {
+ context->output_device_array = devices;
+ context->output_collection_changed_callback(
+ context, context->output_collection_changed_user_ptr);
+ }
+ }
+ });
return noErr;
}
static OSStatus
-audiounit_add_device_listener(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback collection_changed_callback,
- void * user_ptr)
+audiounit_add_device_listener(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
{
+ context->mutex.assert_current_thread_owns();
+ assert(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT));
/* Note: second register without unregister first causes 'nope' error.
* Current implementation requires unregister before register a new cb. */
- assert(context->collection_changed_callback == NULL);
-
- AudioObjectPropertyAddress devAddr;
- devAddr.mSelector = kAudioHardwarePropertyDevices;
- devAddr.mScope = kAudioObjectPropertyScopeGlobal;
- devAddr.mElement = kAudioObjectPropertyElementMaster;
-
- OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject,
- &devAddr,
- audiounit_collection_changed_callback,
- context);
- if (ret == noErr) {
- /* Expected zero after unregister. */
- assert(context->devtype_device_count == 0);
- assert(context->devtype_device_array == NULL);
- /* Listener works for input and output.
- * When requested one of them we need to differentiate. */
- if (devtype == CUBEB_DEVICE_TYPE_INPUT ||
- devtype == CUBEB_DEVICE_TYPE_OUTPUT) {
- /* Used to differentiate input from output device changes. */
- context->devtype_device_count = audiounit_get_devices_of_type(devtype, &context->devtype_device_array);
- }
- context->collection_changed_devtype = devtype;
- context->collection_changed_callback = collection_changed_callback;
- context->collection_changed_user_ptr = user_ptr;
+ assert((devtype & CUBEB_DEVICE_TYPE_INPUT) &&
+ !context->input_collection_changed_callback ||
+ (devtype & CUBEB_DEVICE_TYPE_OUTPUT) &&
+ !context->output_collection_changed_callback);
+
+ if (!context->input_collection_changed_callback &&
+ !context->output_collection_changed_callback) {
+ OSStatus ret = AudioObjectAddPropertyListener(
+ kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS,
+ audiounit_collection_changed_callback, context);
+ if (ret != noErr) {
+ return ret;
+ }
}
- return ret;
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ /* Expected empty after unregister. */
+ assert(context->input_device_array.empty());
+ context->input_device_array =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ context->input_collection_changed_callback = collection_changed_callback;
+ context->input_collection_changed_user_ptr = user_ptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ /* Expected empty after unregister. */
+ assert(context->output_device_array.empty());
+ context->output_device_array =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+ context->output_collection_changed_callback = collection_changed_callback;
+ context->output_collection_changed_user_ptr = user_ptr;
+ }
+ return noErr;
}
static OSStatus
-audiounit_remove_device_listener(cubeb * context)
+audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype)
{
- AudioObjectPropertyAddress devAddr;
- devAddr.mSelector = kAudioHardwarePropertyDevices;
- devAddr.mScope = kAudioObjectPropertyScopeGlobal;
- devAddr.mElement = kAudioObjectPropertyElementMaster;
+ context->mutex.assert_current_thread_owns();
- /* Note: unregister a non registered cb is not a problem, not checking. */
- OSStatus ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
- &devAddr,
- audiounit_collection_changed_callback,
- context);
- if (ret == noErr) {
- /* Reset all values. */
- context->collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN;
- context->collection_changed_callback = NULL;
- context->collection_changed_user_ptr = NULL;
- context->devtype_device_count = 0;
- if (context->devtype_device_array) {
- delete [] context->devtype_device_array;
- context->devtype_device_array = NULL;
- }
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = nullptr;
+ context->input_collection_changed_user_ptr = nullptr;
+ context->input_device_array.clear();
}
- return ret;
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = nullptr;
+ context->output_collection_changed_user_ptr = nullptr;
+ context->output_device_array.clear();
+ }
+
+ if (context->input_collection_changed_callback ||
+ context->output_collection_changed_callback) {
+ return noErr;
+ }
+ /* Note: unregister a non registered cb is not a problem, not checking. */
+ return AudioObjectRemovePropertyListener(
+ kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS,
+ audiounit_collection_changed_callback, context);
}
-int audiounit_register_device_collection_changed(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback collection_changed_callback,
- void * user_ptr)
+int
+audiounit_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
{
+ if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
OSStatus ret;
auto_lock lock(context->mutex);
if (collection_changed_callback) {
ret = audiounit_add_device_listener(context, devtype,
- collection_changed_callback,
- user_ptr);
+ collection_changed_callback, user_ptr);
} else {
- ret = audiounit_remove_device_listener(context);
+ ret = audiounit_remove_device_listener(context, devtype);
}
return (ret == noErr) ? CUBEB_OK : CUBEB_ERROR;
}
cubeb_ops const audiounit_ops = {
- /*.init =*/ audiounit_init,
- /*.get_backend_id =*/ audiounit_get_backend_id,
- /*.get_max_channel_count =*/ audiounit_get_max_channel_count,
- /*.get_min_latency =*/ audiounit_get_min_latency,
- /*.get_preferred_sample_rate =*/ audiounit_get_preferred_sample_rate,
- /*.enumerate_devices =*/ audiounit_enumerate_devices,
- /*.destroy =*/ audiounit_destroy,
- /*.stream_init =*/ audiounit_stream_init,
- /*.stream_destroy =*/ audiounit_stream_destroy,
- /*.stream_start =*/ audiounit_stream_start,
- /*.stream_stop =*/ audiounit_stream_stop,
- /*.stream_get_position =*/ audiounit_stream_get_position,
- /*.stream_get_latency =*/ audiounit_stream_get_latency,
- /*.stream_set_volume =*/ audiounit_stream_set_volume,
- /*.stream_set_panning =*/ audiounit_stream_set_panning,
- /*.stream_get_current_device =*/ audiounit_stream_get_current_device,
- /*.stream_device_destroy =*/ audiounit_stream_device_destroy,
- /*.stream_register_device_changed_callback =*/ audiounit_stream_register_device_changed_callback,
- /*.register_device_collection_changed =*/ audiounit_register_device_collection_changed
-};
+ /*.init =*/audiounit_init,
+ /*.get_backend_id =*/audiounit_get_backend_id,
+ /*.get_max_channel_count =*/audiounit_get_max_channel_count,
+ /*.get_min_latency =*/audiounit_get_min_latency,
+ /*.get_preferred_sample_rate =*/audiounit_get_preferred_sample_rate,
+ /*.enumerate_devices =*/audiounit_enumerate_devices,
+ /*.device_collection_destroy =*/audiounit_device_collection_destroy,
+ /*.destroy =*/audiounit_destroy,
+ /*.stream_init =*/audiounit_stream_init,
+ /*.stream_destroy =*/audiounit_stream_destroy,
+ /*.stream_start =*/audiounit_stream_start,
+ /*.stream_stop =*/audiounit_stream_stop,
+ /*.stream_get_position =*/audiounit_stream_get_position,
+ /*.stream_get_latency =*/audiounit_stream_get_latency,
+ /*.stream_get_input_latency =*/NULL,
+ /*.stream_set_volume =*/audiounit_stream_set_volume,
+ /*.stream_set_name =*/NULL,
+ /*.stream_get_current_device =*/audiounit_stream_get_current_device,
+ /*.stream_device_destroy =*/audiounit_stream_device_destroy,
+ /*.stream_register_device_changed_callback =*/
+ audiounit_stream_register_device_changed_callback,
+ /*.register_device_collection_changed =*/
+ audiounit_register_device_collection_changed};
diff --git a/media/libcubeb/src/cubeb_jack.cpp b/media/libcubeb/src/cubeb_jack.cpp
index 8f995da661..9dc5199e71 100644
--- a/media/libcubeb/src/cubeb_jack.cpp
+++ b/media/libcubeb/src/cubeb_jack.cpp
@@ -8,53 +8,53 @@
*/
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
+#ifndef __FreeBSD__
#define _POSIX_SOURCE
-#include <algorithm>
+#endif
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_resampler.h"
+#include "cubeb_utils.h"
#include <dlfcn.h>
-#include <limits>
-#include <stdio.h>
-#include <sys/time.h>
-#include <assert.h>
-#include <string.h>
#include <limits.h>
-#include <poll.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <pthread.h>
#include <math.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-#include "cubeb_resampler.h"
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include <jack/jack.h>
#include <jack/statistics.h>
-#define JACK_API_VISIT(X) \
- X(jack_activate) \
- X(jack_client_close) \
- X(jack_client_open) \
- X(jack_connect) \
- X(jack_free) \
- X(jack_get_ports) \
- X(jack_get_sample_rate) \
- X(jack_get_xrun_delayed_usecs) \
- X(jack_get_buffer_size) \
- X(jack_port_get_buffer) \
- X(jack_port_name) \
- X(jack_port_register) \
- X(jack_port_unregister) \
- X(jack_port_get_latency_range) \
- X(jack_set_process_callback) \
- X(jack_set_xrun_callback) \
- X(jack_set_graph_order_callback) \
- X(jack_set_error_function) \
+#define JACK_API_VISIT(X) \
+ X(jack_activate) \
+ X(jack_client_close) \
+ X(jack_client_open) \
+ X(jack_connect) \
+ X(jack_free) \
+ X(jack_get_ports) \
+ X(jack_get_sample_rate) \
+ X(jack_get_xrun_delayed_usecs) \
+ X(jack_get_buffer_size) \
+ X(jack_port_get_buffer) \
+ X(jack_port_name) \
+ X(jack_port_register) \
+ X(jack_port_unregister) \
+ X(jack_port_get_latency_range) \
+ X(jack_set_process_callback) \
+ X(jack_set_xrun_callback) \
+ X(jack_set_graph_order_callback) \
+ X(jack_set_error_function) \
X(jack_set_info_function)
#define IMPORT_FUNC(x) static decltype(x) * api_##x;
JACK_API_VISIT(IMPORT_FUNC);
+#define JACK_DEFAULT_IN "JACK capture"
+#define JACK_DEFAULT_OUT "JACK playback"
+
static const int MAX_STREAMS = 16;
-static const int MAX_CHANNELS = 8;
+static const int MAX_CHANNELS = 8;
static const int FIFO_SIZE = 4096 * sizeof(float);
enum devstream {
@@ -64,6 +64,12 @@ enum devstream {
DUPLEX,
};
+enum cbjack_connect_ports_options {
+ CBJACK_CP_OPTIONS_NONE = 0x0,
+ CBJACK_CP_OPTIONS_SKIP_OUTPUT = 0x1,
+ CBJACK_CP_OPTIONS_SKIP_INPUT = 0x2,
+};
+
static void
s16ne_to_float(float * dst, const int16_t * src, size_t n)
{
@@ -75,79 +81,110 @@ static void
float_to_s16ne(int16_t * dst, float * src, size_t n)
{
for (size_t i = 0; i < n; i++) {
- if (*src > 1.f) *src = 1.f;
- if (*src < -1.f) *src = -1.f;
+ if (*src > 1.f)
+ *src = 1.f;
+ if (*src < -1.f)
+ *src = -1.f;
*(dst++) = (int16_t)((int16_t)(*(src++) * 32767));
}
}
-extern "C"
-{
-/*static*/ int jack_init (cubeb ** context, char const * context_name);
+extern "C" {
+/*static*/ int
+jack_init(cubeb ** context, char const * context_name);
}
-static char const * cbjack_get_backend_id(cubeb * context);
-static int cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels);
-static int cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames);
-static int cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_frames);
-static int cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate);
-static void cbjack_destroy(cubeb * context);
-static void cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch);
-static void cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short **bufs_in, float **bufs_out, jack_nframes_t nframes);
-static void cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float **bufs_in, float **bufs_out, jack_nframes_t nframes);
-static int cbjack_stream_device_destroy(cubeb_stream * stream,
- cubeb_device * device);
-static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device);
-static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection);
-static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr);
-static void cbjack_stream_destroy(cubeb_stream * stream);
-static int cbjack_stream_start(cubeb_stream * stream);
-static int cbjack_stream_stop(cubeb_stream * stream);
-static int cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position);
-static int cbjack_stream_set_volume(cubeb_stream * stm, float volume);
+static char const *
+cbjack_get_backend_id(cubeb * context);
+static int
+cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels);
+static int
+cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames);
+static int
+cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_frames);
+static int
+cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate);
+static void
+cbjack_destroy(cubeb * context);
+static void
+cbjack_interleave_capture(cubeb_stream * stream, float ** in,
+ jack_nframes_t nframes, bool format_mismatch);
+static void
+cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream,
+ short ** bufs_in, float ** bufs_out,
+ jack_nframes_t nframes);
+static void
+cbjack_deinterleave_playback_refill_float(cubeb_stream * stream,
+ float ** bufs_in, float ** bufs_out,
+ jack_nframes_t nframes);
+static int
+cbjack_stream_device_destroy(cubeb_stream * stream, cubeb_device * device);
+static int
+cbjack_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device);
+static int
+cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection);
+static int
+cbjack_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection);
+static int
+cbjack_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr);
+static void
+cbjack_stream_destroy(cubeb_stream * stream);
+static int
+cbjack_stream_start(cubeb_stream * stream);
+static int
+cbjack_stream_stop(cubeb_stream * stream);
+static int
+cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position);
+static int
+cbjack_stream_set_volume(cubeb_stream * stm, float volume);
static struct cubeb_ops const cbjack_ops = {
- .init = jack_init,
- .get_backend_id = cbjack_get_backend_id,
- .get_max_channel_count = cbjack_get_max_channel_count,
- .get_min_latency = cbjack_get_min_latency,
- .get_preferred_sample_rate = cbjack_get_preferred_sample_rate,
- .enumerate_devices = cbjack_enumerate_devices,
- .destroy = cbjack_destroy,
- .stream_init = cbjack_stream_init,
- .stream_destroy = cbjack_stream_destroy,
- .stream_start = cbjack_stream_start,
- .stream_stop = cbjack_stream_stop,
- .stream_get_position = cbjack_stream_get_position,
- .stream_get_latency = cbjack_get_latency,
- .stream_set_volume = cbjack_stream_set_volume,
- .stream_set_panning = NULL,
- .stream_get_current_device = cbjack_stream_get_current_device,
- .stream_device_destroy = cbjack_stream_device_destroy,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = NULL
-};
+ .init = jack_init,
+ .get_backend_id = cbjack_get_backend_id,
+ .get_max_channel_count = cbjack_get_max_channel_count,
+ .get_min_latency = cbjack_get_min_latency,
+ .get_preferred_sample_rate = cbjack_get_preferred_sample_rate,
+ .enumerate_devices = cbjack_enumerate_devices,
+ .device_collection_destroy = cbjack_device_collection_destroy,
+ .destroy = cbjack_destroy,
+ .stream_init = cbjack_stream_init,
+ .stream_destroy = cbjack_stream_destroy,
+ .stream_start = cbjack_stream_start,
+ .stream_stop = cbjack_stream_stop,
+ .stream_get_position = cbjack_stream_get_position,
+ .stream_get_latency = cbjack_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = cbjack_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = cbjack_stream_get_current_device,
+ .stream_device_destroy = cbjack_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
+ void * user_ptr;
+ /**/
/**< Mutex for each stream */
pthread_mutex_t mutex;
- bool in_use; /**< Set to false iff the stream is free */
+ bool in_use; /**< Set to false iff the stream is free */
bool ports_ready; /**< Set to true iff the JACK ports are ready */
cubeb_data_callback data_callback;
cubeb_state_callback state_callback;
- void * user_ptr;
cubeb_stream_params in_params;
cubeb_stream_params out_params;
@@ -183,7 +220,6 @@ struct cubeb {
cubeb_stream streams[MAX_STREAMS];
unsigned int active_streams;
- cubeb_device_info * devinfo[2];
cubeb_device_collection_changed_callback collection_changed_callback;
bool active;
@@ -203,25 +239,28 @@ load_jack_lib(cubeb * context)
context->libjack = dlopen("libjack.0.dylib", RTLD_LAZY);
context->libjack = dlopen("/usr/local/lib/libjack.0.dylib", RTLD_LAZY);
#elif defined(__WIN32__)
-# ifdef _WIN64
- context->libjack = LoadLibrary("libjack64.dll");
-# else
- context->libjack = LoadLibrary("libjack.dll");
-# endif
+#ifdef _WIN64
+ context->libjack = LoadLibrary("libjack64.dll");
+#else
+ context->libjack = LoadLibrary("libjack.dll");
+#endif
#else
context->libjack = dlopen("libjack.so.0", RTLD_LAZY);
+ if (!context->libjack) {
+ context->libjack = dlopen("libjack.so", RTLD_LAZY);
+ }
#endif
if (!context->libjack) {
return CUBEB_ERROR;
}
-#define LOAD(x) \
- { \
- api_##x = (decltype(x)*)dlsym(context->libjack, #x); \
- if (!api_##x) { \
- dlclose(context->libjack); \
- return CUBEB_ERROR; \
- } \
+#define LOAD(x) \
+ { \
+ api_##x = (decltype(x) *)dlsym(context->libjack, #x); \
+ if (!api_##x) { \
+ dlclose(context->libjack); \
+ return CUBEB_ERROR; \
+ } \
}
JACK_API_VISIT(LOAD);
@@ -231,41 +270,72 @@ load_jack_lib(cubeb * context)
}
static void
-cbjack_connect_ports (cubeb_stream * stream)
+cbjack_connect_port_out(cubeb_stream * stream, const size_t out_port,
+ const char * const phys_in_port)
+{
+ const char * src_port = api_jack_port_name(stream->output_ports[out_port]);
+
+ api_jack_connect(stream->context->jack_client, src_port, phys_in_port);
+}
+
+static void
+cbjack_connect_port_in(cubeb_stream * stream, const char * const phys_out_port,
+ size_t in_port)
{
- const char ** phys_in_ports = api_jack_get_ports (stream->context->jack_client,
- NULL, NULL,
- JackPortIsInput
- | JackPortIsPhysical);
- const char ** phys_out_ports = api_jack_get_ports (stream->context->jack_client,
- NULL, NULL,
- JackPortIsOutput
- | JackPortIsPhysical);
-
- if (*phys_in_ports == NULL) {
+ const char * src_port = api_jack_port_name(stream->input_ports[in_port]);
+
+ api_jack_connect(stream->context->jack_client, phys_out_port, src_port);
+}
+
+static int
+cbjack_connect_ports(cubeb_stream * stream,
+ enum cbjack_connect_ports_options options)
+{
+ int r = CUBEB_ERROR;
+ const char ** phys_in_ports =
+ api_jack_get_ports(stream->context->jack_client, NULL, NULL,
+ JackPortIsInput | JackPortIsPhysical);
+ const char ** phys_out_ports =
+ api_jack_get_ports(stream->context->jack_client, NULL, NULL,
+ JackPortIsOutput | JackPortIsPhysical);
+
+ if (phys_in_ports == NULL || *phys_in_ports == NULL ||
+ options & CBJACK_CP_OPTIONS_SKIP_OUTPUT) {
goto skipplayback;
}
// Connect outputs to playback
- for (unsigned int c = 0; c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) {
- const char *src_port = api_jack_port_name (stream->output_ports[c]);
+ for (unsigned int c = 0;
+ c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) {
+ cbjack_connect_port_out(stream, c, phys_in_ports[c]);
+ }
- api_jack_connect (stream->context->jack_client, src_port, phys_in_ports[c]);
+ // Special case playing mono source in stereo
+ if (stream->out_params.channels == 1 && phys_in_ports[1] != NULL) {
+ cbjack_connect_port_out(stream, 0, phys_in_ports[1]);
}
+ r = CUBEB_OK;
+
skipplayback:
- if (*phys_out_ports == NULL) {
+ if (phys_out_ports == NULL || *phys_out_ports == NULL ||
+ options & CBJACK_CP_OPTIONS_SKIP_INPUT) {
goto end;
}
// Connect inputs to capture
- for (unsigned int c = 0; c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) {
- const char *src_port = api_jack_port_name (stream->input_ports[c]);
-
- api_jack_connect (stream->context->jack_client, phys_out_ports[c], src_port);
+ for (unsigned int c = 0;
+ c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) {
+ cbjack_connect_port_in(stream, phys_out_ports[c], c);
}
+ r = CUBEB_OK;
end:
- api_jack_free(phys_out_ports);
- api_jack_free(phys_in_ports);
+ if (phys_out_ports) {
+ api_jack_free(phys_out_ports);
+ }
+ if (phys_in_ports) {
+ api_jack_free(phys_in_ports);
+ }
+ return r;
}
static int
@@ -274,9 +344,10 @@ cbjack_xrun_callback(void * arg)
cubeb * ctx = (cubeb *)arg;
float delay = api_jack_get_xrun_delayed_usecs(ctx->jack_client);
- int fragments = (int)ceilf( ((delay / 1000000.0) * ctx->jack_sample_rate )
- / (float)(ctx->jack_buffer_size) );
- ctx->jack_xruns += fragments;
+ float fragments = ceilf(((delay / 1000000.0) * ctx->jack_sample_rate) /
+ ctx->jack_buffer_size);
+
+ ctx->jack_xruns += (unsigned int)fragments;
return 0;
}
@@ -289,7 +360,7 @@ cbjack_graph_order_callback(void * arg)
jack_nframes_t port_latency, max_latency = 0;
for (int j = 0; j < MAX_STREAMS; j++) {
- cubeb_stream *stm = &ctx->streams[j];
+ cubeb_stream * stm = &ctx->streams[j];
if (!stm->in_use)
continue;
@@ -297,10 +368,11 @@ cbjack_graph_order_callback(void * arg)
continue;
for (i = 0; i < (int)stm->out_params.channels; ++i) {
- api_jack_port_get_latency_range(stm->output_ports[i], JackPlaybackLatency, &latency_range);
+ api_jack_port_get_latency_range(stm->output_ports[i], JackPlaybackLatency,
+ &latency_range);
port_latency = latency_range.max;
if (port_latency > max_latency)
- max_latency = port_latency;
+ max_latency = port_latency;
}
/* Cap minimum latency to 128 frames */
if (max_latency < 128)
@@ -316,22 +388,21 @@ static int
cbjack_process(jack_nframes_t nframes, void * arg)
{
cubeb * ctx = (cubeb *)arg;
- int t_jack_xruns = ctx->jack_xruns;
+ unsigned int t_jack_xruns = ctx->jack_xruns;
int i;
+ ctx->jack_xruns = 0;
+
for (int j = 0; j < MAX_STREAMS; j++) {
- cubeb_stream *stm = &ctx->streams[j];
- float *bufs_out[stm->out_params.channels];
- float *bufs_in[stm->in_params.channels];
+ cubeb_stream * stm = &ctx->streams[j];
+ float * bufs_out[stm->out_params.channels];
+ float * bufs_in[stm->in_params.channels];
if (!stm->in_use)
continue;
// handle xruns by skipping audio that should have been played
- for (i = 0; i < t_jack_xruns; i++) {
- stm->position += ctx->fragment_size * stm->ratio;
- }
- ctx->jack_xruns -= t_jack_xruns;
+ stm->position += t_jack_xruns * ctx->fragment_size * stm->ratio;
if (!stm->ports_ready)
continue;
@@ -339,18 +410,20 @@ cbjack_process(jack_nframes_t nframes, void * arg)
if (stm->devs & OUT_ONLY) {
// get jack output buffers
for (i = 0; i < (int)stm->out_params.channels; i++)
- bufs_out[i] = (float*)api_jack_port_get_buffer(stm->output_ports[i], nframes);
+ bufs_out[i] =
+ (float *)api_jack_port_get_buffer(stm->output_ports[i], nframes);
}
if (stm->devs & IN_ONLY) {
// get jack input buffers
for (i = 0; i < (int)stm->in_params.channels; i++)
- bufs_in[i] = (float*)api_jack_port_get_buffer(stm->input_ports[i], nframes);
+ bufs_in[i] =
+ (float *)api_jack_port_get_buffer(stm->input_ports[i], nframes);
}
if (stm->pause) {
// paused, play silence on output
if (stm->devs & OUT_ONLY) {
for (unsigned int c = 0; c < stm->out_params.channels; c++) {
- float* buffer_out = bufs_out[c];
+ float * buffer_out = bufs_out[c];
for (long f = 0; f < nframes; f++) {
buffer_out[f] = 0.f;
}
@@ -359,7 +432,7 @@ cbjack_process(jack_nframes_t nframes, void * arg)
if (stm->devs & IN_ONLY) {
// paused, capture silence
for (unsigned int c = 0; c < stm->in_params.channels; c++) {
- float* buffer_in = bufs_in[c];
+ float * buffer_in = bufs_in[c];
for (long f = 0; f < nframes; f++) {
buffer_in[f] = 0.f;
}
@@ -370,31 +443,38 @@ cbjack_process(jack_nframes_t nframes, void * arg)
// try to lock stream mutex
if (pthread_mutex_trylock(&stm->mutex) == 0) {
- int16_t *in_s16ne = stm->context->in_resampled_interleaved_buffer_s16ne;
- float *in_float = stm->context->in_resampled_interleaved_buffer_float;
+ int16_t * in_s16ne =
+ stm->context->in_resampled_interleaved_buffer_s16ne;
+ float * in_float = stm->context->in_resampled_interleaved_buffer_float;
// unpaused, play audio
if (stm->devs == DUPLEX) {
if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, true);
- cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, bufs_out, nframes);
+ cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, bufs_out,
+ nframes);
} else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, false);
- cbjack_deinterleave_playback_refill_float(stm, &in_float, bufs_out, nframes);
+ cbjack_deinterleave_playback_refill_float(stm, &in_float, bufs_out,
+ nframes);
}
} else if (stm->devs == IN_ONLY) {
if (stm->in_params.format == CUBEB_SAMPLE_S16NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, true);
- cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, nullptr, nframes);
+ cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, nullptr,
+ nframes);
} else if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, false);
- cbjack_deinterleave_playback_refill_float(stm, &in_float, nullptr, nframes);
+ cbjack_deinterleave_playback_refill_float(stm, &in_float, nullptr,
+ nframes);
}
} else if (stm->devs == OUT_ONLY) {
if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
- cbjack_deinterleave_playback_refill_s16ne(stm, nullptr, bufs_out, nframes);
+ cbjack_deinterleave_playback_refill_s16ne(stm, nullptr, bufs_out,
+ nframes);
} else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
- cbjack_deinterleave_playback_refill_float(stm, nullptr, bufs_out, nframes);
+ cbjack_deinterleave_playback_refill_float(stm, nullptr, bufs_out,
+ nframes);
}
}
// unlock stream mutex
@@ -405,7 +485,7 @@ cbjack_process(jack_nframes_t nframes, void * arg)
// output silence
if (stm->devs & OUT_ONLY) {
for (unsigned int c = 0; c < stm->out_params.channels; c++) {
- float* buffer_out = bufs_out[c];
+ float * buffer_out = bufs_out[c];
for (long f = 0; f < nframes; f++) {
buffer_out[f] = 0.f;
}
@@ -414,7 +494,7 @@ cbjack_process(jack_nframes_t nframes, void * arg)
if (stm->devs & IN_ONLY) {
// capture silence
for (unsigned int c = 0; c < stm->in_params.channels; c++) {
- float* buffer_in = bufs_in[c];
+ float * buffer_in = bufs_in[c];
for (long f = 0; f < nframes; f++) {
buffer_in[f] = 0.f;
}
@@ -426,9 +506,10 @@ cbjack_process(jack_nframes_t nframes, void * arg)
return 0;
}
-
static void
-cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes)
+cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in,
+ float ** bufs_out,
+ jack_nframes_t nframes)
{
float * out_interleaved_buffer = nullptr;
@@ -439,21 +520,24 @@ cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, fl
long done_frames = 0;
long input_frames_count = (in != NULL) ? nframes : 0;
+ done_frames = cubeb_resampler_fill(
+ stream->resampler, inptr, &input_frames_count,
+ (bufs_out != NULL)
+ ? stream->context->out_resampled_interleaved_buffer_float
+ : NULL,
+ needed_frames);
- done_frames = cubeb_resampler_fill(stream->resampler,
- inptr,
- &input_frames_count,
- (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_float : NULL,
- needed_frames);
-
- out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float;
+ out_interleaved_buffer =
+ stream->context->out_resampled_interleaved_buffer_float;
if (outptr) {
// convert interleaved output buffers to contiguous buffers
for (unsigned int c = 0; c < stream->out_params.channels; c++) {
- float* buffer = bufs_out[c];
+ float * buffer = bufs_out[c];
for (long f = 0; f < done_frames; f++) {
- buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume;
+ buffer[f] =
+ out_interleaved_buffer[(f * stream->out_params.channels) + c] *
+ stream->volume;
}
if (done_frames < needed_frames) {
// draining
@@ -487,7 +571,9 @@ cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, fl
}
static void
-cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, float ** bufs_out, jack_nframes_t nframes)
+cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in,
+ float ** bufs_out,
+ jack_nframes_t nframes)
{
float * out_interleaved_buffer = nullptr;
@@ -498,22 +584,28 @@ cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, fl
long done_frames = 0;
long input_frames_count = (in != NULL) ? nframes : 0;
- done_frames = cubeb_resampler_fill(stream->resampler,
- inptr,
- &input_frames_count,
- (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_s16ne : NULL,
- needed_frames);
+ done_frames = cubeb_resampler_fill(
+ stream->resampler, inptr, &input_frames_count,
+ (bufs_out != NULL)
+ ? stream->context->out_resampled_interleaved_buffer_s16ne
+ : NULL,
+ needed_frames);
- s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float, stream->context->out_resampled_interleaved_buffer_s16ne, done_frames * stream->out_params.channels);
+ s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float,
+ stream->context->out_resampled_interleaved_buffer_s16ne,
+ done_frames * stream->out_params.channels);
- out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float;
+ out_interleaved_buffer =
+ stream->context->out_resampled_interleaved_buffer_float;
if (outptr) {
// convert interleaved output buffers to contiguous buffers
for (unsigned int c = 0; c < stream->out_params.channels; c++) {
- float* buffer = bufs_out[c];
+ float * buffer = bufs_out[c];
for (long f = 0; f < done_frames; f++) {
- buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume;
+ buffer[f] =
+ out_interleaved_buffer[(f * stream->out_params.channels) + c] *
+ stream->volume;
}
if (done_frames < needed_frames) {
// draining
@@ -547,20 +639,25 @@ cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, fl
}
static void
-cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch)
+cbjack_interleave_capture(cubeb_stream * stream, float ** in,
+ jack_nframes_t nframes, bool format_mismatch)
{
- float *in_buffer = stream->context->in_float_interleaved_buffer;
+ float * in_buffer = stream->context->in_float_interleaved_buffer;
for (unsigned int c = 0; c < stream->in_params.channels; c++) {
for (long f = 0; f < nframes; f++) {
- in_buffer[(f * stream->in_params.channels) + c] = in[c][f] * stream->volume;
+ in_buffer[(f * stream->in_params.channels) + c] =
+ in[c][f] * stream->volume;
}
}
if (format_mismatch) {
- float_to_s16ne(stream->context->in_resampled_interleaved_buffer_s16ne, in_buffer, nframes * stream->in_params.channels);
+ float_to_s16ne(stream->context->in_resampled_interleaved_buffer_s16ne,
+ in_buffer, nframes * stream->in_params.channels);
} else {
- memset(stream->context->in_resampled_interleaved_buffer_float, 0, (FIFO_SIZE * MAX_CHANNELS * 3) * sizeof(float));
- memcpy(stream->context->in_resampled_interleaved_buffer_float, in_buffer, (FIFO_SIZE * MAX_CHANNELS * 2) * sizeof(float));
+ memset(stream->context->in_resampled_interleaved_buffer_float, 0,
+ (FIFO_SIZE * MAX_CHANNELS * 3) * sizeof(float));
+ memcpy(stream->context->in_resampled_interleaved_buffer_float, in_buffer,
+ (FIFO_SIZE * MAX_CHANNELS * 2) * sizeof(float));
}
}
@@ -570,7 +667,7 @@ silent_jack_error_callback(char const * /*msg*/)
}
/*static*/ int
-jack_init (cubeb ** context, char const * context_name)
+jack_init(cubeb ** context, char const * context_name)
{
int r;
@@ -601,9 +698,8 @@ jack_init (cubeb ** context, char const * context_name)
if (context_name)
jack_client_name = context_name;
- ctx->jack_client = api_jack_client_open(jack_client_name,
- JackNoStartServer,
- NULL);
+ ctx->jack_client =
+ api_jack_client_open(jack_client_name, JackNoStartServer, NULL);
if (ctx->jack_client == NULL) {
cbjack_destroy(ctx);
@@ -612,11 +708,12 @@ jack_init (cubeb ** context, char const * context_name)
ctx->jack_xruns = 0;
- api_jack_set_process_callback (ctx->jack_client, cbjack_process, ctx);
- api_jack_set_xrun_callback (ctx->jack_client, cbjack_xrun_callback, ctx);
- api_jack_set_graph_order_callback (ctx->jack_client, cbjack_graph_order_callback, ctx);
+ api_jack_set_process_callback(ctx->jack_client, cbjack_process, ctx);
+ api_jack_set_xrun_callback(ctx->jack_client, cbjack_xrun_callback, ctx);
+ api_jack_set_graph_order_callback(ctx->jack_client,
+ cbjack_graph_order_callback, ctx);
- if (api_jack_activate (ctx->jack_client)) {
+ if (api_jack_activate(ctx->jack_client)) {
cbjack_destroy(ctx);
return CUBEB_ERROR;
}
@@ -651,7 +748,8 @@ cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_ms)
}
static int
-cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params /*params*/, uint32_t * latency_ms)
+cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params /*params*/,
+ uint32_t * latency_ms)
{
*latency_ms = ctx->jack_latency;
return CUBEB_OK;
@@ -661,9 +759,8 @@ static int
cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
{
if (!ctx->jack_client) {
- jack_client_t * testclient = api_jack_client_open("test-samplerate",
- JackNoStartServer,
- NULL);
+ jack_client_t * testclient =
+ api_jack_client_open("test-samplerate", JackNoStartServer, NULL);
if (!testclient) {
return CUBEB_ERROR;
}
@@ -683,7 +780,7 @@ cbjack_destroy(cubeb * context)
context->active = false;
if (context->jack_client != NULL)
- api_jack_client_close (context->jack_client);
+ api_jack_client_close(context->jack_client);
if (context->libjack)
dlclose(context->libjack);
@@ -706,35 +803,42 @@ context_alloc_stream(cubeb * context, char const * stream_name)
}
static int
-cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
+cbjack_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
unsigned int /*latency_frames*/,
cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
+ cubeb_state_callback state_callback, void * user_ptr)
{
int stream_actual_rate = 0;
int jack_rate = api_jack_get_sample_rate(context->jack_client);
- if (output_stream_params
- && (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
- output_stream_params->format != CUBEB_SAMPLE_S16NE)
- ) {
+ if (output_stream_params &&
+ (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
+ output_stream_params->format != CUBEB_SAMPLE_S16NE)) {
return CUBEB_ERROR_INVALID_FORMAT;
}
- if (input_stream_params
- && (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
- input_stream_params->format != CUBEB_SAMPLE_S16NE)
- ) {
+ if (input_stream_params &&
+ (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
+ input_stream_params->format != CUBEB_SAMPLE_S16NE)) {
return CUBEB_ERROR_INVALID_FORMAT;
}
- if (input_device || output_device)
+ if ((input_device && input_device != JACK_DEFAULT_IN) ||
+ (output_device && output_device != JACK_DEFAULT_OUT)) {
return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ // Loopback is unsupported
+ if ((input_stream_params &&
+ (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) ||
+ (output_stream_params &&
+ (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
*stream = NULL;
@@ -812,29 +916,17 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
stm->resampler = NULL;
if (stm->devs == DUPLEX) {
- stm->resampler = cubeb_resampler_create(stm,
- &stm->in_params,
- &stm->out_params,
- stream_actual_rate,
- stm->data_callback,
- stm->user_ptr,
- CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ stm->resampler = cubeb_resampler_create(
+ stm, &stm->in_params, &stm->out_params, stream_actual_rate,
+ stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
} else if (stm->devs == IN_ONLY) {
- stm->resampler = cubeb_resampler_create(stm,
- &stm->in_params,
- nullptr,
- stream_actual_rate,
- stm->data_callback,
- stm->user_ptr,
- CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ stm->resampler = cubeb_resampler_create(
+ stm, &stm->in_params, nullptr, stream_actual_rate, stm->data_callback,
+ stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
} else if (stm->devs == OUT_ONLY) {
- stm->resampler = cubeb_resampler_create(stm,
- nullptr,
- &stm->out_params,
- stream_actual_rate,
- stm->data_callback,
- stm->user_ptr,
- CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ stm->resampler = cubeb_resampler_create(
+ stm, nullptr, &stm->out_params, stream_actual_rate, stm->data_callback,
+ stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
}
if (!stm->resampler) {
@@ -847,11 +939,18 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
for (unsigned int c = 0; c < stm->out_params.channels; c++) {
char portname[256];
snprintf(portname, 255, "%s_out_%d", stm->stream_name, c);
- stm->output_ports[c] = api_jack_port_register(stm->context->jack_client,
- portname,
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsOutput,
- 0);
+ stm->output_ports[c] =
+ api_jack_port_register(stm->context->jack_client, portname,
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!(output_stream_params->prefs &
+ CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT)) {
+ if (cbjack_connect_ports(stm, CBJACK_CP_OPTIONS_SKIP_INPUT) !=
+ CUBEB_OK) {
+ pthread_mutex_unlock(&stm->mutex);
+ cbjack_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+ }
}
}
@@ -859,16 +958,21 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
for (unsigned int c = 0; c < stm->in_params.channels; c++) {
char portname[256];
snprintf(portname, 255, "%s_in_%d", stm->stream_name, c);
- stm->input_ports[c] = api_jack_port_register(stm->context->jack_client,
- portname,
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsInput,
- 0);
+ stm->input_ports[c] =
+ api_jack_port_register(stm->context->jack_client, portname,
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
+ if (!(input_stream_params->prefs &
+ CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT)) {
+ if (cbjack_connect_ports(stm, CBJACK_CP_OPTIONS_SKIP_OUTPUT) !=
+ CUBEB_OK) {
+ pthread_mutex_unlock(&stm->mutex);
+ cbjack_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+ }
}
}
- cbjack_connect_ports(stm);
-
*stream = stm;
stm->ports_ready = true;
@@ -887,7 +991,8 @@ cbjack_stream_destroy(cubeb_stream * stream)
if (stream->devs == DUPLEX || stream->devs == OUT_ONLY) {
for (unsigned int c = 0; c < stream->out_params.channels; c++) {
if (stream->output_ports[c]) {
- api_jack_port_unregister (stream->context->jack_client, stream->output_ports[c]);
+ api_jack_port_unregister(stream->context->jack_client,
+ stream->output_ports[c]);
stream->output_ports[c] = NULL;
}
}
@@ -896,7 +1001,8 @@ cbjack_stream_destroy(cubeb_stream * stream)
if (stream->devs == DUPLEX || stream->devs == IN_ONLY) {
for (unsigned int c = 0; c < stream->in_params.channels; c++) {
if (stream->input_ports[c]) {
- api_jack_port_unregister (stream->context->jack_client, stream->input_ports[c]);
+ api_jack_port_unregister(stream->context->jack_client,
+ stream->input_ports[c]);
stream->input_ports[c] = NULL;
}
}
@@ -940,16 +1046,16 @@ cbjack_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_OK;
}
-
static int
-cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device)
+cbjack_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device)
{
*device = (cubeb_device *)calloc(1, sizeof(cubeb_device));
if (*device == NULL)
return CUBEB_ERROR;
- const char * j_in = "JACK capture";
- const char * j_out = "JACK playback";
+ const char * j_in = JACK_DEFAULT_IN;
+ const char * j_out = JACK_DEFAULT_OUT;
const char * empty = "";
if (stm->devs == DUPLEX) {
@@ -967,8 +1073,7 @@ cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const devic
}
static int
-cbjack_stream_device_destroy(cubeb_stream * /*stream*/,
- cubeb_device * device)
+cbjack_stream_device_destroy(cubeb_stream * /*stream*/, cubeb_device * device)
{
if (device->input_name)
free(device->input_name);
@@ -980,68 +1085,72 @@ cbjack_stream_device_destroy(cubeb_stream * /*stream*/,
static int
cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection)
+ cubeb_device_collection * collection)
{
if (!context)
return CUBEB_ERROR;
uint32_t rate;
- uint8_t i = 0;
- uint8_t j;
cbjack_get_preferred_sample_rate(context, &rate);
- const char * j_in = "JACK capture";
- const char * j_out = "JACK playback";
+
+ cubeb_device_info * devices = new cubeb_device_info[2];
+ if (!devices)
+ return CUBEB_ERROR;
+ PodZero(devices, 2);
+ collection->count = 0;
if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
- context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info));
- context->devinfo[i]->device_id = strdup(j_out);
- context->devinfo[i]->devid = context->devinfo[i]->device_id;
- context->devinfo[i]->friendly_name = strdup(j_out);
- context->devinfo[i]->group_id = strdup(j_out);
- context->devinfo[i]->vendor_name = strdup(j_out);
- context->devinfo[i]->type = CUBEB_DEVICE_TYPE_OUTPUT;
- context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED;
- context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL;
- context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE;
- context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE;
- context->devinfo[i]->max_channels = MAX_CHANNELS;
- context->devinfo[i]->min_rate = rate;
- context->devinfo[i]->max_rate = rate;
- context->devinfo[i]->default_rate = rate;
- context->devinfo[i]->latency_lo = 0;
- context->devinfo[i]->latency_hi = 0;
- i++;
+ cubeb_device_info * cur = &devices[collection->count];
+ cur->device_id = JACK_DEFAULT_OUT;
+ cur->devid = (cubeb_devid)cur->device_id;
+ cur->friendly_name = JACK_DEFAULT_OUT;
+ cur->group_id = JACK_DEFAULT_OUT;
+ cur->vendor_name = JACK_DEFAULT_OUT;
+ cur->type = CUBEB_DEVICE_TYPE_OUTPUT;
+ cur->state = CUBEB_DEVICE_STATE_ENABLED;
+ cur->preferred = CUBEB_DEVICE_PREF_ALL;
+ cur->format = CUBEB_DEVICE_FMT_F32NE;
+ cur->default_format = CUBEB_DEVICE_FMT_F32NE;
+ cur->max_channels = MAX_CHANNELS;
+ cur->min_rate = rate;
+ cur->max_rate = rate;
+ cur->default_rate = rate;
+ cur->latency_lo = 0;
+ cur->latency_hi = 0;
+ collection->count += 1;
}
if (type & CUBEB_DEVICE_TYPE_INPUT) {
- context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info));
- context->devinfo[i]->device_id = strdup(j_in);
- context->devinfo[i]->devid = context->devinfo[i]->device_id;
- context->devinfo[i]->friendly_name = strdup(j_in);
- context->devinfo[i]->group_id = strdup(j_in);
- context->devinfo[i]->vendor_name = strdup(j_in);
- context->devinfo[i]->type = CUBEB_DEVICE_TYPE_INPUT;
- context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED;
- context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL;
- context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE;
- context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE;
- context->devinfo[i]->max_channels = MAX_CHANNELS;
- context->devinfo[i]->min_rate = rate;
- context->devinfo[i]->max_rate = rate;
- context->devinfo[i]->default_rate = rate;
- context->devinfo[i]->latency_lo = 0;
- context->devinfo[i]->latency_hi = 0;
- i++;
+ cubeb_device_info * cur = &devices[collection->count];
+ cur->device_id = JACK_DEFAULT_IN;
+ cur->devid = (cubeb_devid)cur->device_id;
+ cur->friendly_name = JACK_DEFAULT_IN;
+ cur->group_id = JACK_DEFAULT_IN;
+ cur->vendor_name = JACK_DEFAULT_IN;
+ cur->type = CUBEB_DEVICE_TYPE_INPUT;
+ cur->state = CUBEB_DEVICE_STATE_ENABLED;
+ cur->preferred = CUBEB_DEVICE_PREF_ALL;
+ cur->format = CUBEB_DEVICE_FMT_F32NE;
+ cur->default_format = CUBEB_DEVICE_FMT_F32NE;
+ cur->max_channels = MAX_CHANNELS;
+ cur->min_rate = rate;
+ cur->max_rate = rate;
+ cur->default_rate = rate;
+ cur->latency_lo = 0;
+ cur->latency_hi = 0;
+ collection->count += 1;
}
- *collection = (cubeb_device_collection *)
- malloc(sizeof(cubeb_device_collection) +
- i * sizeof(cubeb_device_info *));
+ collection->device = devices;
- (*collection)->count = i;
+ return CUBEB_OK;
+}
- for (j = 0; j < i; j++) {
- (*collection)->device[j] = context->devinfo[j];
- }
+static int
+cbjack_device_collection_destroy(cubeb * /*ctx*/,
+ cubeb_device_collection * collection)
+{
+ XASSERT(collection);
+ delete[] collection->device;
return CUBEB_OK;
}
diff --git a/media/libcubeb/src/cubeb_log.cpp b/media/libcubeb/src/cubeb_log.cpp
new file mode 100644
index 0000000000..ff72e0e87a
--- /dev/null
+++ b/media/libcubeb/src/cubeb_log.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#define NOMINMAX
+
+#include "cubeb_log.h"
+#include "cubeb_ringbuffer.h"
+#include <cstdarg>
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <time.h>
+#endif
+
+cubeb_log_level g_cubeb_log_level;
+cubeb_log_callback g_cubeb_log_callback;
+
+/** The maximum size of a log message, after having been formatted. */
+const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
+/** The maximum number of log messages that can be queued before dropping
+ * messages. */
+const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
+/** Number of milliseconds to wait before dequeuing log messages. */
+#define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10
+
+/**
+ * This wraps an inline buffer, that represents a log message, that must be
+ * null-terminated.
+ * This class should not use system calls or other potentially blocking code.
+ */
+class cubeb_log_message {
+public:
+ cubeb_log_message() { *storage = '\0'; }
+ cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
+ {
+ size_t length = strlen(str);
+ /* paranoia against malformed message */
+ assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE);
+ if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) {
+ return;
+ }
+ PodCopy(storage, str, length);
+ storage[length] = '\0';
+ }
+ char const * get() { return storage; }
+
+private:
+ char storage[CUBEB_LOG_MESSAGE_MAX_SIZE];
+};
+
+/** Lock-free asynchronous logger, made so that logging from a
+ * real-time audio callback does not block the audio thread. */
+class cubeb_async_logger {
+public:
+ /* This is thread-safe since C++11 */
+ static cubeb_async_logger & get()
+ {
+ static cubeb_async_logger instance;
+ return instance;
+ }
+ void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
+ {
+ cubeb_log_message msg(str);
+ msg_queue.enqueue(msg);
+ }
+ void run()
+ {
+ std::thread([this]() {
+ while (true) {
+ cubeb_log_message msg;
+ while (msg_queue.dequeue(&msg, 1)) {
+ LOGV("%s", msg.get());
+ }
+#ifdef _WIN32
+ Sleep(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS);
+#else
+ timespec sleep_duration = sleep_for;
+ timespec remainder;
+ do {
+ if (nanosleep(&sleep_duration, &remainder) == 0 || errno != EINTR) {
+ break;
+ }
+ sleep_duration = remainder;
+ } while (remainder.tv_sec || remainder.tv_nsec);
+#endif
+ }
+ }).detach();
+ }
+ // Tell the underlying queue the producer thread has changed, so it does not
+ // assert in debug. This should be called with the thread stopped.
+ void reset_producer_thread() { msg_queue.reset_thread_ids(); }
+
+private:
+#ifndef _WIN32
+ const struct timespec sleep_for = {
+ CUBEB_LOG_BATCH_PRINT_INTERVAL_MS / 1000,
+ (CUBEB_LOG_BATCH_PRINT_INTERVAL_MS % 1000) * 1000 * 1000};
+#endif
+ cubeb_async_logger() : msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH) { run(); }
+ /** This is quite a big data structure, but is only instantiated if the
+ * asynchronous logger is used.*/
+ lock_free_queue<cubeb_log_message> msg_queue;
+};
+
+void
+cubeb_async_log(char const * fmt, ...)
+{
+ if (!g_cubeb_log_callback) {
+ return;
+ }
+ // This is going to copy a 256 bytes array around, which is fine.
+ // We don't want to allocate memory here, because this is made to
+ // be called from a real-time callback.
+ va_list args;
+ va_start(args, fmt);
+ char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
+ vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
+ cubeb_async_logger::get().push(msg);
+ va_end(args);
+}
+
+void
+cubeb_async_log_reset_threads()
+{
+ if (!g_cubeb_log_callback) {
+ return;
+ }
+ cubeb_async_logger::get().reset_producer_thread();
+}
diff --git a/media/libcubeb/src/cubeb_log.h b/media/libcubeb/src/cubeb_log.h
index bca98c96fe..aee31805e0 100644
--- a/media/libcubeb/src/cubeb_log.h
+++ b/media/libcubeb/src/cubeb_log.h
@@ -8,18 +8,34 @@
#ifndef CUBEB_LOG
#define CUBEB_LOG
+#include "cubeb/cubeb.h"
+
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__GNUC__) || defined(__clang__)
#define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args)))
+#if defined(__FILE_NAME__)
+#define __FILENAME__ __FILE_NAME__
+#else
+#define __FILENAME__ \
+ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 \
+ : __FILE__)
+#endif
#else
#define PRINTF_FORMAT(fmt, args)
+#include <string.h>
+#define __FILENAME__ \
+ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#endif
-extern cubeb_log_level g_log_level;
-extern cubeb_log_callback g_log_callback PRINTF_FORMAT(1, 2);
+extern cubeb_log_level g_cubeb_log_level;
+extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2);
+void
+cubeb_async_log(const char * fmt, ...);
+void
+cubeb_async_log_reset_threads();
#ifdef __cplusplus
}
@@ -28,10 +44,19 @@ extern cubeb_log_callback g_log_callback PRINTF_FORMAT(1, 2);
#define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
#define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
-#define LOG_INTERNAL(level, fmt, ...) do { \
- if (g_log_callback && level <= g_log_level) { \
- g_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
- } \
- } while(0)
+#define LOG_INTERNAL(level, fmt, ...) \
+ do { \
+ if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
+ g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, \
+ ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+/* Asynchronous verbose logging, to log in real-time callbacks. */
+/* Should not be used on android due to the use of global/static variables. */
+#define ALOGV(fmt, ...) \
+ do { \
+ cubeb_async_log(fmt, ##__VA_ARGS__); \
+ } while (0)
#endif // CUBEB_LOG
diff --git a/media/libcubeb/src/cubeb_mixer.cpp b/media/libcubeb/src/cubeb_mixer.cpp
new file mode 100644
index 0000000000..343e0e2d39
--- /dev/null
+++ b/media/libcubeb/src/cubeb_mixer.cpp
@@ -0,0 +1,625 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ *
+ * Adapted from code based on libswresample's rematrix.c
+ */
+
+#define NOMINMAX
+
+#include "cubeb_mixer.h"
+#include "cubeb-internal.h"
+#include "cubeb_utils.h"
+#include <algorithm>
+#include <cassert>
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <memory>
+#include <type_traits>
+
+#ifndef FF_ARRAY_ELEMS
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#define CHANNELS_MAX 32
+#define FRONT_LEFT 0
+#define FRONT_RIGHT 1
+#define FRONT_CENTER 2
+#define LOW_FREQUENCY 3
+#define BACK_LEFT 4
+#define BACK_RIGHT 5
+#define FRONT_LEFT_OF_CENTER 6
+#define FRONT_RIGHT_OF_CENTER 7
+#define BACK_CENTER 8
+#define SIDE_LEFT 9
+#define SIDE_RIGHT 10
+#define TOP_CENTER 11
+#define TOP_FRONT_LEFT 12
+#define TOP_FRONT_CENTER 13
+#define TOP_FRONT_RIGHT 14
+#define TOP_BACK_LEFT 15
+#define TOP_BACK_CENTER 16
+#define TOP_BACK_RIGHT 17
+#define NUM_NAMED_CHANNELS 18
+
+#ifndef M_SQRT1_2
+#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
+#endif
+#ifndef M_SQRT2
+#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
+#endif
+#define SQRT3_2 1.22474487139158904909 /* sqrt(3/2) */
+
+#define C30DB M_SQRT2
+#define C15DB 1.189207115
+#define C__0DB 1.0
+#define C_15DB 0.840896415
+#define C_30DB M_SQRT1_2
+#define C_45DB 0.594603558
+#define C_60DB 0.5
+
+static cubeb_channel_layout
+cubeb_channel_layout_check(cubeb_channel_layout l, uint32_t c)
+{
+ if (l == CUBEB_LAYOUT_UNDEFINED) {
+ switch (c) {
+ case 1:
+ return CUBEB_LAYOUT_MONO;
+ case 2:
+ return CUBEB_LAYOUT_STEREO;
+ }
+ }
+ return l;
+}
+
+unsigned int
+cubeb_channel_layout_nb_channels(cubeb_channel_layout x)
+{
+#if __GNUC__ || __clang__
+ return __builtin_popcount(x);
+#else
+ x -= (x >> 1) & 0x55555555;
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x + (x >> 4)) & 0x0F0F0F0F;
+ x += x >> 8;
+ return (x + (x >> 16)) & 0x3F;
+#endif
+}
+
+struct MixerContext {
+ MixerContext(cubeb_sample_format f, uint32_t in_channels,
+ cubeb_channel_layout in, uint32_t out_channels,
+ cubeb_channel_layout out)
+ : _format(f), _in_ch_layout(cubeb_channel_layout_check(in, in_channels)),
+ _out_ch_layout(cubeb_channel_layout_check(out, out_channels)),
+ _in_ch_count(in_channels), _out_ch_count(out_channels)
+ {
+ if (in_channels != cubeb_channel_layout_nb_channels(in) ||
+ out_channels != cubeb_channel_layout_nb_channels(out)) {
+ // Mismatch between channels and layout, aborting.
+ return;
+ }
+ _valid = init() >= 0;
+ }
+
+ static bool even(cubeb_channel_layout layout)
+ {
+ if (!layout) {
+ return true;
+ }
+ if (layout & (layout - 1)) {
+ return true;
+ }
+ return false;
+ }
+
+ // Ensure that the layout is sane (that is have symmetrical left/right
+ // channels), if not, layout will be treated as mono.
+ static cubeb_channel_layout clean_layout(cubeb_channel_layout layout)
+ {
+ if (layout && layout != CHANNEL_FRONT_LEFT && !(layout & (layout - 1))) {
+ LOG("Treating layout as mono");
+ return CHANNEL_FRONT_CENTER;
+ }
+
+ return layout;
+ }
+
+ static bool sane_layout(cubeb_channel_layout layout)
+ {
+ if (!(layout & CUBEB_LAYOUT_3F)) { // at least 1 front speaker
+ return false;
+ }
+ if (!even(layout & (CHANNEL_FRONT_LEFT |
+ CHANNEL_FRONT_RIGHT))) { // no asymetric front
+ return false;
+ }
+ if (!even(layout &
+ (CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT))) { // no asymetric side
+ return false;
+ }
+ if (!even(layout & (CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT))) {
+ return false;
+ }
+ if (!even(layout &
+ (CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER))) {
+ return false;
+ }
+ if (cubeb_channel_layout_nb_channels(layout) >= CHANNELS_MAX) {
+ return false;
+ }
+ return true;
+ }
+
+ int auto_matrix();
+ int init();
+
+ const cubeb_sample_format _format;
+ const cubeb_channel_layout _in_ch_layout; ///< input channel layout
+ const cubeb_channel_layout _out_ch_layout; ///< output channel layout
+ const uint32_t _in_ch_count; ///< input channel count
+ const uint32_t _out_ch_count; ///< output channel count
+ const float _surround_mix_level = C_30DB; ///< surround mixing level
+ const float _center_mix_level = C_30DB; ///< center mixing level
+ const float _lfe_mix_level = 1; ///< LFE mixing level
+ double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {
+ {0}}; ///< floating point rematrixing coefficients
+ float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {
+ {0}}; ///< single precision floating point rematrixing coefficients
+ int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {
+ {0}}; ///< 17.15 fixed point rematrixing coefficients
+ uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX + 1] = {
+ {0}}; ///< Lists of input channels per output channel that have non zero
+ ///< rematrixing coefficients
+ bool _clipping = false; ///< Set to true if clipping detection is required
+ bool _valid = false; ///< Set to true if context is valid.
+};
+
+int
+MixerContext::auto_matrix()
+{
+ double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = {{0}};
+ double maxcoef = 0;
+ float maxval;
+
+ cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout);
+ cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout);
+
+ if (!sane_layout(in_ch_layout)) {
+ // Channel Not Supported
+ LOG("Input Layout %x is not supported", _in_ch_layout);
+ return -1;
+ }
+
+ if (!sane_layout(out_ch_layout)) {
+ LOG("Output Layout %x is not supported", _out_ch_layout);
+ return -1;
+ }
+
+ for (uint32_t i = 0; i < FF_ARRAY_ELEMS(matrix); i++) {
+ if (in_ch_layout & out_ch_layout & (1U << i)) {
+ matrix[i][i] = 1.0;
+ }
+ }
+
+ cubeb_channel_layout unaccounted = in_ch_layout & ~out_ch_layout;
+
+ // Rematrixing is done via a matrix of coefficient that should be applied to
+ // all channels. Channels are treated as pair and must be symmetrical (if a
+ // left channel exists, the corresponding right should exist too) unless the
+ // output layout has similar layout. Channels are then mixed toward the front
+ // center or back center if they exist with a slight bias toward the front.
+
+ if (unaccounted & CHANNEL_FRONT_CENTER) {
+ if ((out_ch_layout & CUBEB_LAYOUT_STEREO) == CUBEB_LAYOUT_STEREO) {
+ if (in_ch_layout & CUBEB_LAYOUT_STEREO) {
+ matrix[FRONT_LEFT][FRONT_CENTER] += _center_mix_level;
+ matrix[FRONT_RIGHT][FRONT_CENTER] += _center_mix_level;
+ } else {
+ matrix[FRONT_LEFT][FRONT_CENTER] += M_SQRT1_2;
+ matrix[FRONT_RIGHT][FRONT_CENTER] += M_SQRT1_2;
+ }
+ }
+ }
+ if (unaccounted & CUBEB_LAYOUT_STEREO) {
+ if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][FRONT_LEFT] += M_SQRT1_2;
+ matrix[FRONT_CENTER][FRONT_RIGHT] += M_SQRT1_2;
+ if (in_ch_layout & CHANNEL_FRONT_CENTER)
+ matrix[FRONT_CENTER][FRONT_CENTER] = _center_mix_level * M_SQRT2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_BACK_CENTER) {
+ if (out_ch_layout & CHANNEL_BACK_LEFT) {
+ matrix[BACK_LEFT][BACK_CENTER] += M_SQRT1_2;
+ matrix[BACK_RIGHT][BACK_CENTER] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+ matrix[SIDE_LEFT][BACK_CENTER] += M_SQRT1_2;
+ matrix[SIDE_RIGHT][BACK_CENTER] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+ if (unaccounted & CHANNEL_BACK_LEFT) {
+ if (out_ch_layout & CHANNEL_BACK_CENTER) {
+ matrix[BACK_CENTER][BACK_LEFT] += M_SQRT1_2;
+ matrix[BACK_CENTER][BACK_RIGHT] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+ if (in_ch_layout & CHANNEL_SIDE_LEFT) {
+ matrix[SIDE_LEFT][BACK_LEFT] += M_SQRT1_2;
+ matrix[SIDE_RIGHT][BACK_RIGHT] += M_SQRT1_2;
+ } else {
+ matrix[SIDE_LEFT][BACK_LEFT] += 1.0;
+ matrix[SIDE_RIGHT][BACK_RIGHT] += 1.0;
+ }
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][BACK_LEFT] += _surround_mix_level;
+ matrix[FRONT_RIGHT][BACK_RIGHT] += _surround_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][BACK_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_CENTER][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_SIDE_LEFT) {
+ if (out_ch_layout & CHANNEL_BACK_LEFT) {
+ /* if back channels do not exist in the input, just copy side
+ channels to back channels, otherwise mix side into back */
+ if (in_ch_layout & CHANNEL_BACK_LEFT) {
+ matrix[BACK_LEFT][SIDE_LEFT] += M_SQRT1_2;
+ matrix[BACK_RIGHT][SIDE_RIGHT] += M_SQRT1_2;
+ } else {
+ matrix[BACK_LEFT][SIDE_LEFT] += 1.0;
+ matrix[BACK_RIGHT][SIDE_RIGHT] += 1.0;
+ }
+ } else if (out_ch_layout & CHANNEL_BACK_CENTER) {
+ matrix[BACK_CENTER][SIDE_LEFT] += M_SQRT1_2;
+ matrix[BACK_CENTER][SIDE_RIGHT] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][SIDE_LEFT] += _surround_mix_level;
+ matrix[FRONT_RIGHT][SIDE_RIGHT] += _surround_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_CENTER][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_FRONT_LEFT_OF_CENTER) {
+ if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0;
+ matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += M_SQRT1_2;
+ matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += M_SQRT1_2;
+ }
+ }
+ /* mix LFE into front left/right or center */
+ if (unaccounted & CHANNEL_LOW_FREQUENCY) {
+ if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][LOW_FREQUENCY] += _lfe_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+ }
+ }
+
+ // Normalize the conversion matrix.
+ for (uint32_t out_i = 0, i = 0; i < CHANNELS_MAX; i++) {
+ double sum = 0;
+ int in_i = 0;
+ if ((out_ch_layout & (1U << i)) == 0) {
+ continue;
+ }
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ if ((in_ch_layout & (1U << j)) == 0) {
+ continue;
+ }
+ if (i < FF_ARRAY_ELEMS(matrix) && j < FF_ARRAY_ELEMS(matrix[0])) {
+ _matrix[out_i][in_i] = matrix[i][j];
+ } else {
+ _matrix[out_i][in_i] =
+ i == j && (in_ch_layout & out_ch_layout & (1U << i));
+ }
+ sum += fabs(_matrix[out_i][in_i]);
+ in_i++;
+ }
+ maxcoef = std::max(maxcoef, sum);
+ out_i++;
+ }
+
+ if (_format == CUBEB_SAMPLE_S16NE) {
+ maxval = 1.0;
+ } else {
+ maxval = INT_MAX;
+ }
+
+ // Normalize matrix if needed.
+ if (maxcoef > maxval) {
+ maxcoef /= maxval;
+ for (uint32_t i = 0; i < CHANNELS_MAX; i++)
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ _matrix[i][j] /= maxcoef;
+ }
+ }
+
+ if (_format == CUBEB_SAMPLE_FLOAT32NE) {
+ for (uint32_t i = 0; i < FF_ARRAY_ELEMS(_matrix); i++) {
+ for (uint32_t j = 0; j < FF_ARRAY_ELEMS(_matrix[0]); j++) {
+ _matrix_flt[i][j] = _matrix[i][j];
+ }
+ }
+ }
+
+ return 0;
+}
+
+int
+MixerContext::init()
+{
+ int r = auto_matrix();
+ if (r) {
+ return r;
+ }
+
+ // Determine if matrix operation would overflow
+ if (_format == CUBEB_SAMPLE_S16NE) {
+ int maxsum = 0;
+ for (uint32_t i = 0; i < _out_ch_count; i++) {
+ double rem = 0;
+ int sum = 0;
+
+ for (uint32_t j = 0; j < _in_ch_count; j++) {
+ double target = _matrix[i][j] * 32768 + rem;
+ int value = lrintf(target);
+ rem += target - value;
+ sum += std::abs(value);
+ }
+ maxsum = std::max(maxsum, sum);
+ }
+ if (maxsum > 32768) {
+ _clipping = true;
+ }
+ }
+
+ // FIXME quantize for integers
+ for (uint32_t i = 0; i < CHANNELS_MAX; i++) {
+ int ch_in = 0;
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ _matrix32[i][j] = lrintf(_matrix[i][j] * 32768);
+ if (_matrix[i][j]) {
+ _matrix_ch[i][++ch_in] = j;
+ }
+ }
+ _matrix_ch[i][0] = ch_in;
+ }
+
+ return 0;
+}
+
+template <typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
+void
+sum2(TYPE_SAMPLE * out, uint32_t stride_out, const TYPE_SAMPLE * in1,
+ const TYPE_SAMPLE * in2, uint32_t stride_in, TYPE_COEFF coeff1,
+ TYPE_COEFF coeff2, F && operand, uint32_t frames)
+{
+ static_assert(
+ std::is_same<TYPE_COEFF,
+ typename std::result_of<F(TYPE_COEFF)>::type>::value,
+ "function must return the same type as used by matrix_coeff");
+ for (uint32_t i = 0; i < frames; i++) {
+ *out = operand(coeff1 * *in1 + coeff2 * *in2);
+ out += stride_out;
+ in1 += stride_in;
+ in2 += stride_in;
+ }
+}
+
+template <typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
+void
+copy(TYPE_SAMPLE * out, uint32_t stride_out, const TYPE_SAMPLE * in,
+ uint32_t stride_in, TYPE_COEFF coeff, F && operand, uint32_t frames)
+{
+ static_assert(
+ std::is_same<TYPE_COEFF,
+ typename std::result_of<F(TYPE_COEFF)>::type>::value,
+ "function must return the same type as used by matrix_coeff");
+ for (uint32_t i = 0; i < frames; i++) {
+ *out = operand(coeff * *in);
+ out += stride_out;
+ in += stride_in;
+ }
+}
+
+template <typename TYPE, typename TYPE_COEFF, size_t COLS, typename F>
+static int
+rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn,
+ const TYPE_COEFF (&matrix_coeff)[COLS][COLS], F && aF, uint32_t frames)
+{
+ static_assert(
+ std::is_same<TYPE_COEFF,
+ typename std::result_of<F(TYPE_COEFF)>::type>::value,
+ "function must return the same type as used by matrix_coeff");
+
+ for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) {
+ TYPE * out = aOut + out_i;
+ switch (s->_matrix_ch[out_i][0]) {
+ case 0:
+ for (uint32_t i = 0; i < frames; i++) {
+ out[i * s->_out_ch_count] = 0;
+ }
+ break;
+ case 1: {
+ int in_i = s->_matrix_ch[out_i][1];
+ copy(out, s->_out_ch_count, aIn + in_i, s->_in_ch_count,
+ matrix_coeff[out_i][in_i], aF, frames);
+ } break;
+ case 2:
+ sum2(out, s->_out_ch_count, aIn + s->_matrix_ch[out_i][1],
+ aIn + s->_matrix_ch[out_i][2], s->_in_ch_count,
+ matrix_coeff[out_i][s->_matrix_ch[out_i][1]],
+ matrix_coeff[out_i][s->_matrix_ch[out_i][2]], aF, frames);
+ break;
+ default:
+ for (uint32_t i = 0; i < frames; i++) {
+ TYPE_COEFF v = 0;
+ for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) {
+ uint32_t in_i = s->_matrix_ch[out_i][1 + j];
+ v += *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
+ }
+ out[i * s->_out_ch_count] = aF(v);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+struct cubeb_mixer {
+ cubeb_mixer(cubeb_sample_format format, uint32_t in_channels,
+ cubeb_channel_layout in_layout, uint32_t out_channels,
+ cubeb_channel_layout out_layout)
+ : _context(format, in_channels, in_layout, out_channels, out_layout)
+ {
+ }
+
+ template <typename T>
+ void copy_and_trunc(size_t frames, const T * input_buffer,
+ T * output_buffer) const
+ {
+ if (_context._in_ch_count <= _context._out_ch_count) {
+ // Not enough channels to copy, fill the gaps with silence.
+ if (_context._in_ch_count == 1 && _context._out_ch_count >= 2) {
+ // Special case for upmixing mono input to stereo and more. We will
+ // duplicate the mono channel to the first two channels. On most system,
+ // the first two channels are for left and right. It is commonly
+ // expected that mono will on both left+right channels
+ for (uint32_t i = 0; i < frames; i++) {
+ output_buffer[0] = output_buffer[1] = *input_buffer;
+ PodZero(output_buffer + 2, _context._out_ch_count - 2);
+ output_buffer += _context._out_ch_count;
+ input_buffer++;
+ }
+ return;
+ }
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._in_ch_count);
+ output_buffer += _context._in_ch_count;
+ input_buffer += _context._in_ch_count;
+ PodZero(output_buffer, _context._out_ch_count - _context._in_ch_count);
+ output_buffer += _context._out_ch_count - _context._in_ch_count;
+ }
+ } else {
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._out_ch_count);
+ output_buffer += _context._out_ch_count;
+ input_buffer += _context._in_ch_count;
+ }
+ }
+ }
+
+ int mix(size_t frames, const void * input_buffer, size_t input_buffer_size,
+ void * output_buffer, size_t output_buffer_size) const
+ {
+ if (frames <= 0 || _context._out_ch_count == 0) {
+ return 0;
+ }
+
+ // Check if output buffer is of sufficient size.
+ size_t size_read_needed =
+ frames * _context._in_ch_count * cubeb_sample_size(_context._format);
+ if (input_buffer_size < size_read_needed) {
+ // We don't have enough data to read!
+ return -1;
+ }
+ if (output_buffer_size * _context._in_ch_count <
+ size_read_needed * _context._out_ch_count) {
+ return -1;
+ }
+
+ if (!valid()) {
+ // The channel layouts were invalid or unsupported, instead we will simply
+ // either drop the extra channels, or fill with silence the missing ones
+ if (_context._format == CUBEB_SAMPLE_FLOAT32NE) {
+ copy_and_trunc(frames, static_cast<const float *>(input_buffer),
+ static_cast<float *>(output_buffer));
+ } else {
+ assert(_context._format == CUBEB_SAMPLE_S16NE);
+ copy_and_trunc(frames, static_cast<const int16_t *>(input_buffer),
+ reinterpret_cast<int16_t *>(output_buffer));
+ }
+ return 0;
+ }
+
+ switch (_context._format) {
+ case CUBEB_SAMPLE_FLOAT32NE: {
+ auto f = [](float x) { return x; };
+ return rematrix(&_context, static_cast<float *>(output_buffer),
+ static_cast<const float *>(input_buffer),
+ _context._matrix_flt, f, frames);
+ }
+ case CUBEB_SAMPLE_S16NE:
+ if (_context._clipping) {
+ auto f = [](int x) {
+ int y = (x + 16384) >> 15;
+ // clip the signed integer value into the -32768,32767 range.
+ if ((y + 0x8000U) & ~0xFFFF) {
+ return (y >> 31) ^ 0x7FFF;
+ }
+ return y;
+ };
+ return rematrix(&_context, static_cast<int16_t *>(output_buffer),
+ static_cast<const int16_t *>(input_buffer),
+ _context._matrix32, f, frames);
+ } else {
+ auto f = [](int x) { return (x + 16384) >> 15; };
+ return rematrix(&_context, static_cast<int16_t *>(output_buffer),
+ static_cast<const int16_t *>(input_buffer),
+ _context._matrix32, f, frames);
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ return -1;
+ }
+
+ // Return false if any of the input or ouput layout were invalid.
+ bool valid() const { return _context._valid; }
+
+ virtual ~cubeb_mixer(){};
+
+ MixerContext _context;
+};
+
+cubeb_mixer *
+cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels,
+ cubeb_channel_layout in_layout, uint32_t out_channels,
+ cubeb_channel_layout out_layout)
+{
+ return new cubeb_mixer(format, in_channels, in_layout, out_channels,
+ out_layout);
+}
+
+void
+cubeb_mixer_destroy(cubeb_mixer * mixer)
+{
+ delete mixer;
+}
+
+int
+cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
+ size_t input_buffer_size, void * output_buffer,
+ size_t output_buffer_size)
+{
+ return mixer->mix(frames, input_buffer, input_buffer_size, output_buffer,
+ output_buffer_size);
+}
diff --git a/media/libcubeb/src/cubeb_mixer.h b/media/libcubeb/src/cubeb_mixer.h
new file mode 100644
index 0000000000..1859dab467
--- /dev/null
+++ b/media/libcubeb/src/cubeb_mixer.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_MIXER
+#define CUBEB_MIXER
+
+#include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params.
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct cubeb_mixer cubeb_mixer;
+cubeb_mixer *
+cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels,
+ cubeb_channel_layout in_layout, uint32_t out_channels,
+ cubeb_channel_layout out_layout);
+void
+cubeb_mixer_destroy(cubeb_mixer * mixer);
+int
+cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
+ size_t input_buffer_size, void * output_buffer,
+ size_t output_buffer_size);
+
+unsigned int
+cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // CUBEB_MIXER
diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c
index dd54162280..78096d5dd4 100644
--- a/media/libcubeb/src/cubeb_opensl.c
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -5,96 +5,262 @@
* accompanying file LICENSE for details.
*/
#undef NDEBUG
+#include <SLES/OpenSLES.h>
#include <assert.h>
#include <dlfcn.h>
-#include <stdlib.h>
-#include <pthread.h>
-#include <SLES/OpenSLES.h>
+#include <errno.h>
#include <math.h>
+#include <pthread.h>
+#include <stdlib.h>
#include <time.h>
#if defined(__ANDROID__)
-#include <dlfcn.h>
-#include <sys/system_properties.h>
#include "android/sles_definitions.h"
#include <SLES/OpenSLES_Android.h>
-#include <android/log.h>
#include <android/api-level.h>
-#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args)
+#include <android/log.h>
+#include <dlfcn.h>
+#include <sys/system_properties.h>
+#endif
+#include "android/cubeb-output-latency.h"
+#include "cubeb-internal.h"
+#include "cubeb-sles.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_android.h"
+#include "cubeb_array_queue.h"
+#include "cubeb_resampler.h"
+
+#if defined(__ANDROID__)
+#ifdef LOG
+#undef LOG
+#endif
+//#define LOGGING_ENABLED
+#ifdef LOGGING_ENABLED
+#define LOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL", ##args)
+#else
+#define LOG(...)
+#endif
+
+//#define TIMESTAMP_ENABLED
+#ifdef TIMESTAMP_ENABLED
+#define FILENAME \
+ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
+#define LOG_TS(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)", \
+ ##args)
+#define TIMESTAMP(msg) \
+ do { \
+ struct timeval timestamp; \
+ int ts_ret = gettimeofday(&timestamp, NULL); \
+ if (ts_ret == 0) { \
+ LOG_TS("%lld: %s (%s %s:%d)", \
+ timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, \
+ __FUNCTION__, FILENAME, __LINE__); \
+ } else { \
+ LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, \
+ __LINE__); \
+ } \
+ } while (0)
+#else
+#define TIMESTAMP(...)
+#endif
+
#define ANDROID_VERSION_GINGERBREAD_MR1 10
+#define ANDROID_VERSION_JELLY_BEAN 18
#define ANDROID_VERSION_LOLLIPOP 21
#define ANDROID_VERSION_MARSHMALLOW 23
+#define ANDROID_VERSION_N_MR1 25
#endif
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-#include "cubeb_resampler.h"
-#include "cubeb-sles.h"
+
+#define DEFAULT_SAMPLE_RATE 48000
+#define DEFAULT_NUM_OF_FRAMES 480
static struct cubeb_ops const opensl_ops;
struct cubeb {
struct cubeb_ops const * ops;
void * lib;
- void * libmedia;
- int32_t (* get_output_latency)(uint32_t * latency, int stream_type);
SLInterfaceID SL_IID_BUFFERQUEUE;
SLInterfaceID SL_IID_PLAY;
#if defined(__ANDROID__)
SLInterfaceID SL_IID_ANDROIDCONFIGURATION;
+ SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
#endif
SLInterfaceID SL_IID_VOLUME;
+ SLInterfaceID SL_IID_RECORD;
SLObjectItf engObj;
SLEngineItf eng;
SLObjectItf outmixObj;
+ output_latency_function * p_output_latency_function;
};
#define NELEMS(A) (sizeof(A) / sizeof A[0])
-#define NBUFS 4
-#define AUDIO_STREAM_TYPE_MUSIC 3
+#define NBUFS 2
struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
+ void * user_ptr;
+ /**/
pthread_mutex_t mutex;
SLObjectItf playerObj;
SLPlayItf play;
SLBufferQueueItf bufq;
SLVolumeItf volume;
- uint8_t *queuebuf[NBUFS];
+ void ** queuebuf;
+ uint32_t queuebuf_capacity;
int queuebuf_idx;
long queuebuf_len;
long bytespersec;
long framesize;
+ /* Total number of played frames.
+ * Synchronized by stream::mutex lock. */
long written;
+ /* Flag indicating draining. Synchronized
+ * by stream::mutex lock. */
int draining;
- cubeb_stream_type stream_type;
-
+ /* Flags to determine in/out.*/
+ uint32_t input_enabled;
+ uint32_t output_enabled;
+ /* Recorder abstract object. */
+ SLObjectItf recorderObj;
+ /* Recorder Itf for input capture. */
+ SLRecordItf recorderItf;
+ /* Buffer queue for input capture. */
+ SLAndroidSimpleBufferQueueItf recorderBufferQueueItf;
+ /* Store input buffers. */
+ void ** input_buffer_array;
+ /* The capacity of the array.
+ * On capture only can be small (4).
+ * On full duplex is calculated to
+ * store 1 sec of data buffers. */
+ uint32_t input_array_capacity;
+ /* Current filled index of input buffer array.
+ * It is initiated to -1 indicating buffering
+ * have not started yet. */
+ int input_buffer_index;
+ /* Length of input buffer.*/
+ uint32_t input_buffer_length;
+ /* Input frame size */
+ uint32_t input_frame_size;
+ /* Device sampling rate. If user rate is not
+ * accepted an compatible rate is set. If it is
+ * accepted this is equal to params.rate. */
+ uint32_t input_device_rate;
+ /* Exchange input buffers between input
+ * and full duplex threads. */
+ array_queue * input_queue;
+ /* Silent input buffer used on full duplex. */
+ void * input_silent_buffer;
+ /* Number of input frames from the start of the stream*/
+ uint32_t input_total_frames;
+ /* Flag to stop the execution of user callback and
+ * close all working threads. Synchronized by
+ * stream::mutex lock. */
+ uint32_t shutdown;
+ /* Store user callback. */
cubeb_data_callback data_callback;
+ /* Store state callback. */
cubeb_state_callback state_callback;
- void * user_ptr;
cubeb_resampler * resampler;
- unsigned int inputrate;
- unsigned int outputrate;
- unsigned int latency;
+ unsigned int user_output_rate;
+ unsigned int output_configured_rate;
+ unsigned int buffer_size_frames;
+ // Audio output latency used in cubeb_stream_get_position().
+ unsigned int output_latency_ms;
int64_t lastPosition;
int64_t lastPositionTimeStamp;
int64_t lastCompensativePosition;
+ int voice_input;
+ int voice_output;
};
+/* Forward declaration. */
+static int
+opensl_stop_player(cubeb_stream * stm);
+static int
+opensl_stop_recorder(cubeb_stream * stm);
+
+static int
+opensl_get_draining(cubeb_stream * stm)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "get_draining: mutex should be locked but it's not.");
+#endif
+ return stm->draining;
+}
+
+static void
+opensl_set_draining(cubeb_stream * stm, int value)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ LOG("set draining try r = %d", r);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "set_draining: mutex should be locked but it's not.");
+#endif
+ assert(value == 0 || value == 1);
+ stm->draining = value;
+}
+
+static void
+opensl_notify_drained(cubeb_stream * stm)
+{
+ assert(stm);
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (draining) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ if (stm->play) {
+ LOG("stop player in play_callback");
+ r = opensl_stop_player(stm);
+ assert(r == CUBEB_OK);
+ }
+ if (stm->recorderItf) {
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+ }
+}
+
+static uint32_t
+opensl_get_shutdown(cubeb_stream * stm)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "get_shutdown: mutex should be locked but it's not.");
+#endif
+ return stm->shutdown;
+}
+
+static void
+opensl_set_shutdown(cubeb_stream * stm, uint32_t value)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ LOG("set shutdown try r = %d", r);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "set_shutdown: mutex should be locked but it's not.");
+#endif
+ assert(value == 0 || value == 1);
+ stm->shutdown = value;
+}
+
static void
play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event)
{
cubeb_stream * stm = user_ptr;
- int draining;
assert(stm);
switch (event) {
case SL_PLAYEVENT_HEADATMARKER:
- pthread_mutex_lock(&stm->mutex);
- draining = stm->draining;
- pthread_mutex_unlock(&stm->mutex);
- if (draining) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
- (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
- }
+ opensl_notify_drained(stm);
break;
default:
break;
@@ -102,104 +268,372 @@ play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event)
}
static void
+recorder_marker_callback(SLRecordItf caller, void * pContext, SLuint32 event)
+{
+ cubeb_stream * stm = pContext;
+ assert(stm);
+
+ if (event == SL_RECORDEVENT_HEADATMARKER) {
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (draining) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ if (stm->recorderItf) {
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+ if (stm->play) {
+ r = opensl_stop_player(stm);
+ assert(r == CUBEB_OK);
+ }
+ }
+ }
+}
+
+static void
bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
{
cubeb_stream * stm = user_ptr;
assert(stm);
SLBufferQueueState state;
SLresult res;
+ long written = 0;
res = (*stm->bufq)->GetState(stm->bufq, &state);
assert(res == SL_RESULT_SUCCESS);
- if (state.count > 1)
+ if (state.count > 1) {
return;
+ }
- SLuint32 i;
- for (i = state.count; i < NBUFS; i++) {
- uint8_t *buf = stm->queuebuf[stm->queuebuf_idx];
- long written = 0;
- pthread_mutex_lock(&stm->mutex);
- int draining = stm->draining;
- pthread_mutex_unlock(&stm->mutex);
-
- if (!draining) {
- written = cubeb_resampler_fill(stm->resampler,
- NULL, NULL,
- buf, stm->queuebuf_len / stm->framesize);
- if (written < 0 || written * stm->framesize > stm->queuebuf_len) {
- (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
- return;
- }
+ uint8_t * buf = stm->queuebuf[stm->queuebuf_idx];
+ written = 0;
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (!draining && !shutdown) {
+ written = cubeb_resampler_fill(stm->resampler, NULL, NULL, buf,
+ stm->queuebuf_len / stm->framesize);
+ LOG("bufferqueue_callback: resampler fill returned %ld frames", written);
+ if (written < 0 || written * stm->framesize > stm->queuebuf_len) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ opensl_stop_player(stm);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return;
}
+ }
- // Keep sending silent data even in draining mode to prevent the audio
- // back-end from being stopped automatically by OpenSL/ES.
- memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize);
- res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len);
- assert(res == SL_RESULT_SUCCESS);
- stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS;
- if (written > 0) {
- pthread_mutex_lock(&stm->mutex);
- stm->written += written;
- pthread_mutex_unlock(&stm->mutex);
- }
+ // Keep sending silent data even in draining mode to prevent the audio
+ // back-end from being stopped automatically by OpenSL/ES.
+ assert(stm->queuebuf_len >= written * stm->framesize);
+ memset(buf + written * stm->framesize, 0,
+ stm->queuebuf_len - written * stm->framesize);
+ res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
+
+ if (written > 0) {
+ pthread_mutex_lock(&stm->mutex);
+ stm->written += written;
+ pthread_mutex_unlock(&stm->mutex);
+ }
- if (!draining && written * stm->framesize < stm->queuebuf_len) {
- pthread_mutex_lock(&stm->mutex);
- int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
- stm->draining = 1;
- pthread_mutex_unlock(&stm->mutex);
+ if (!draining && written * stm->framesize < stm->queuebuf_len) {
+ LOG("bufferqueue_callback draining");
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int64_t written_duration =
+ INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (written_duration == 0) {
+ // since we didn't write any sample, it's not possible to reach the marker
+ // time and trigger the callback. We should initiative notify drained.
+ opensl_notify_drained(stm);
+ } else {
// Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
// to make sure all the data has been processed.
- (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
- return;
+ (*stm->play)
+ ->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
}
+ return;
}
}
-#if defined(__ANDROID__)
-static SLuint32
-convert_stream_type_to_sl_stream(cubeb_stream_type stream_type)
-{
- switch(stream_type) {
- case CUBEB_STREAM_TYPE_SYSTEM:
- return SL_ANDROID_STREAM_SYSTEM;
- case CUBEB_STREAM_TYPE_MUSIC:
- return SL_ANDROID_STREAM_MEDIA;
- case CUBEB_STREAM_TYPE_NOTIFICATION:
- return SL_ANDROID_STREAM_NOTIFICATION;
- case CUBEB_STREAM_TYPE_ALARM:
- return SL_ANDROID_STREAM_ALARM;
- case CUBEB_STREAM_TYPE_VOICE_CALL:
- return SL_ANDROID_STREAM_VOICE;
- case CUBEB_STREAM_TYPE_RING:
- return SL_ANDROID_STREAM_RING;
- case CUBEB_STREAM_TYPE_SYSTEM_ENFORCED:
- return SL_ANDROID_STREAM_SYSTEM_ENFORCED;
- default:
- return 0xFFFFFFFF;
+static int
+opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer)
+{
+ assert(stm);
+
+ int current_index = stm->input_buffer_index;
+ void * last_buffer = NULL;
+
+ if (current_index < 0) {
+ // This is the first enqueue
+ current_index = 0;
+ } else {
+ // The current index hold the last filled buffer get it before advance
+ // index.
+ last_buffer = stm->input_buffer_array[current_index];
+ // Advance to get next available buffer
+ current_index = (current_index + 1) % stm->input_array_capacity;
}
+ // enqueue next empty buffer to be filled by the recorder
+ SLresult res = (*stm->recorderBufferQueueItf)
+ ->Enqueue(stm->recorderBufferQueueItf,
+ stm->input_buffer_array[current_index],
+ stm->input_buffer_length);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Enqueue recorder failed. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // All good, update buffer and index.
+ stm->input_buffer_index = current_index;
+ if (last_filled_buffer) {
+ *last_filled_buffer = last_buffer;
+ }
+ return CUBEB_OK;
}
-#endif
-static void opensl_destroy(cubeb * ctx);
+// input data callback
+void
+recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
+{
+ assert(context);
+ cubeb_stream * stm = context;
+ assert(stm->recorderBufferQueueItf);
-#if defined(__ANDROID__)
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ // According to the OpenSL ES 1.1 Specification, 8.14 SLBufferQueueItf
+ // page 184, on transition to the SL_RECORDSTATE_STOPPED state,
+ // the application should continue to enqueue buffers onto the queue
+ // to retrieve the residual recorded data in the system.
+ r = opensl_enqueue_recorder(stm, NULL);
+ assert(r == CUBEB_OK);
+ return;
+ }
+ // Enqueue next available buffer and get the last filled buffer.
+ void * input_buffer = NULL;
+ r = opensl_enqueue_recorder(stm, &input_buffer);
+ assert(r == CUBEB_OK);
+ assert(input_buffer);
+ // Fill resampler with last input
+ long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
+ long got = cubeb_resampler_fill(stm->resampler, input_buffer,
+ &input_frame_count, NULL, 0);
+ // Error case
+ if (got < 0 || got > input_frame_count) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ }
+
+ // Advance total stream frames
+ stm->input_total_frames += got;
+
+ if (got < input_frame_count) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ int64_t duration =
+ INT64_C(1000) * stm->input_total_frames / stm->input_device_rate;
+ (*stm->recorderItf)
+ ->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration);
+ return;
+ }
+}
+
+void
+recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
+{
+ assert(context);
+ cubeb_stream * stm = context;
+ assert(stm->recorderBufferQueueItf);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ /* On draining and shutdown the recorder should have been stoped from
+ * the one set the flags. Accordint to the doc, on transition to
+ * the SL_RECORDSTATE_STOPPED state, the application should
+ * continue to enqueue buffers onto the queue to retrieve the residual
+ * recorded data in the system. */
+ LOG("Input shutdown %d or drain %d", shutdown, draining);
+ int r = opensl_enqueue_recorder(stm, NULL);
+ assert(r == CUBEB_OK);
+ return;
+ }
+
+ // Enqueue next available buffer and get the last filled buffer.
+ void * input_buffer = NULL;
+ r = opensl_enqueue_recorder(stm, &input_buffer);
+ assert(r == CUBEB_OK);
+ assert(input_buffer);
+
+ assert(stm->input_queue);
+ r = array_queue_push(stm->input_queue, input_buffer);
+ if (r == -1) {
+ LOG("Input queue is full, drop input ...");
+ return;
+ }
+
+ LOG("Input pushed in the queue, input array %zu",
+ array_queue_get_size(stm->input_queue));
+}
+
+static void
+player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr)
+{
+ TIMESTAMP("ENTER");
+ cubeb_stream * stm = user_ptr;
+ assert(stm);
+ SLresult res;
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ // Get output
+ void * output_buffer = NULL;
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ output_buffer = stm->queuebuf[stm->queuebuf_idx];
+ // Advance the output buffer queue index
+ stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ LOG("Shutdown/draining, send silent");
+ // Set silent on buffer
+ memset(output_buffer, 0, stm->queuebuf_len);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ return;
+ }
+
+ // Get input.
+ void * input_buffer = array_queue_pop(stm->input_queue);
+ long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
+ long frames_needed = stm->queuebuf_len / stm->framesize;
+ if (!input_buffer) {
+ LOG("Input hole set silent input buffer");
+ input_buffer = stm->input_silent_buffer;
+ }
+
+ long written = 0;
+ // Trigger user callback through resampler
+ written =
+ cubeb_resampler_fill(stm->resampler, input_buffer, &input_frame_count,
+ output_buffer, frames_needed);
+
+ LOG("Fill: written %ld, frames_needed %ld, input array size %zu", written,
+ frames_needed, array_queue_get_size(stm->input_queue));
+
+ if (written < 0 || written > frames_needed) {
+ // Error case
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ opensl_stop_player(stm);
+ opensl_stop_recorder(stm);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ memset(output_buffer, 0, stm->queuebuf_len);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ return;
+ }
+
+ // Advance total out written frames counter
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ stm->written += written;
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (written < frames_needed) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int64_t written_duration =
+ INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
+ // to make sure all the data has been processed.
+ (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
+ }
+
+ // Keep sending silent data even in draining mode to prevent the audio
+ // back-end from being stopped automatically by OpenSL/ES.
+ memset((uint8_t *)output_buffer + written * stm->framesize, 0,
+ stm->queuebuf_len - written * stm->framesize);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ TIMESTAMP("EXIT");
+}
+
+static void
+opensl_destroy(cubeb * ctx);
+
+#if defined(__ANDROID__)
#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
-typedef int (system_property_get)(const char*, char*);
+typedef int(system_property_get)(const char *, char *);
static int
-__system_property_get(const char* name, char* value)
+wrap_system_property_get(const char * name, char * value)
{
- void* libc = dlopen("libc.so", RTLD_LAZY);
+ void * libc = dlopen("libc.so", RTLD_LAZY);
if (!libc) {
LOG("Failed to open libc.so");
return -1;
}
- system_property_get* func = (system_property_get*)
- dlsym(libc, "__system_property_get");
+ system_property_get * func =
+ (system_property_get *)dlsym(libc, "__system_property_get");
int ret = -1;
if (func) {
ret = func(name, value);
@@ -216,14 +650,18 @@ get_android_version(void)
memset(version_string, 0, PROP_VALUE_MAX);
+#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+ int len = wrap_system_property_get("ro.build.version.sdk", version_string);
+#else
int len = __system_property_get("ro.build.version.sdk", version_string);
+#endif
if (len <= 0) {
LOG("Failed to get Android version!\n");
return len;
}
int version = (int)strtol(version_string, NULL, 10);
- LOG("%d", version);
+ LOG("Android version %d", version);
return version;
}
#endif
@@ -235,7 +673,8 @@ opensl_init(cubeb ** context, char const * context_name)
#if defined(__ANDROID__)
int android_version = get_android_version();
- if (android_version > 0 && android_version <= ANDROID_VERSION_GINGERBREAD_MR1) {
+ if (android_version > 0 &&
+ android_version <= ANDROID_VERSION_GINGERBREAD_MR1) {
// Don't even attempt to run on Gingerbread and lower
return CUBEB_ERROR;
}
@@ -249,54 +688,39 @@ opensl_init(cubeb ** context, char const * context_name)
ctx->ops = &opensl_ops;
ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY);
- ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY);
- if (!ctx->lib || !ctx->libmedia) {
+ if (!ctx->lib) {
free(ctx);
return CUBEB_ERROR;
}
- /* Get the latency, in ms, from AudioFlinger */
- /* status_t AudioSystem::getOutputLatency(uint32_t* latency,
- * audio_stream_type_t streamType) */
- /* First, try the most recent signature. */
- ctx->get_output_latency =
- dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
- if (!ctx->get_output_latency) {
- /* in case of failure, try the legacy version. */
- /* status_t AudioSystem::getOutputLatency(uint32_t* latency,
- * int streamType) */
- ctx->get_output_latency =
- dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
- if (!ctx->get_output_latency) {
- opensl_destroy(ctx);
- return CUBEB_ERROR;
- }
- }
-
- typedef SLresult (*slCreateEngine_t)(SLObjectItf *,
- SLuint32,
- const SLEngineOption *,
- SLuint32,
- const SLInterfaceID *,
- const SLboolean *);
+ typedef SLresult (*slCreateEngine_t)(
+ SLObjectItf *, SLuint32, const SLEngineOption *, SLuint32,
+ const SLInterfaceID *, const SLboolean *);
slCreateEngine_t f_slCreateEngine =
- (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
- SLInterfaceID SL_IID_ENGINE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE");
- SLInterfaceID SL_IID_OUTPUTMIX = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX");
+ (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
+ SLInterfaceID SL_IID_ENGINE =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE");
+ SLInterfaceID SL_IID_OUTPUTMIX =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX");
ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME");
- ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE");
+ ctx->SL_IID_BUFFERQUEUE =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE");
#if defined(__ANDROID__)
- ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION");
+ ctx->SL_IID_ANDROIDCONFIGURATION =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION");
+ ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
#endif
ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY");
- if (!f_slCreateEngine ||
- !SL_IID_ENGINE ||
- !SL_IID_OUTPUTMIX ||
+ ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD");
+
+ if (!f_slCreateEngine || !SL_IID_ENGINE || !SL_IID_OUTPUTMIX ||
!ctx->SL_IID_BUFFERQUEUE ||
#if defined(__ANDROID__)
!ctx->SL_IID_ANDROIDCONFIGURATION ||
+ !ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE ||
#endif
- !ctx->SL_IID_PLAY) {
+ !ctx->SL_IID_PLAY || !ctx->SL_IID_RECORD) {
opensl_destroy(ctx);
return CUBEB_ERROR;
}
@@ -325,7 +749,8 @@ opensl_init(cubeb ** context, char const * context_name)
const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX};
const SLboolean reqom[] = {SL_BOOLEAN_TRUE};
- res = (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom);
+ res =
+ (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom);
if (res != SL_RESULT_SUCCESS) {
opensl_destroy(ctx);
return CUBEB_ERROR;
@@ -337,8 +762,16 @@ opensl_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR;
}
+ ctx->p_output_latency_function =
+ cubeb_output_latency_load_method(android_version);
+ if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) {
+ LOG("Warning: output latency is not available, cubeb_stream_get_position() "
+ "is not supported");
+ }
+
*context = ctx;
+ LOG("Cubeb init (%p) success", ctx);
return CUBEB_OK;
}
@@ -353,225 +786,365 @@ opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{
assert(ctx && max_channels);
/* The android mixer handles up to two channels, see
- http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
+ http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67
+ */
*max_channels = 2;
return CUBEB_OK;
}
-static int
-opensl_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
-{
- /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html
- * We don't want to deal with JNI here (and we don't have Java on b2g anyways),
- * so we just dlopen the library and get the two symbols we need. */
- int r;
- void * libmedia;
- uint32_t (*get_primary_output_samplingrate)();
- uint32_t (*get_output_samplingrate)(int * samplingRate, int streamType);
-
- libmedia = dlopen("libmedia.so", RTLD_LAZY);
- if (!libmedia) {
- return CUBEB_ERROR;
- }
+static void
+opensl_destroy(cubeb * ctx)
+{
+ if (ctx->outmixObj)
+ (*ctx->outmixObj)->Destroy(ctx->outmixObj);
+ if (ctx->engObj)
+ cubeb_destroy_sles_engine(&ctx->engObj);
+ dlclose(ctx->lib);
+ if (ctx->p_output_latency_function)
+ cubeb_output_latency_unload_method(ctx->p_output_latency_function);
+ free(ctx);
+}
- /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */
- get_primary_output_samplingrate =
- dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv");
- if (!get_primary_output_samplingrate) {
- /* fallback to
- * status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType)
- * if we cannot find getPrimaryOutputSamplingRate. */
- get_output_samplingrate =
- dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t");
- if (!get_output_samplingrate) {
- /* Another signature exists, with a int instead of an audio_stream_type_t */
- get_output_samplingrate =
- dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPii");
- if (!get_output_samplingrate) {
- dlclose(libmedia);
- return CUBEB_ERROR;
- }
- }
- }
+static void
+opensl_stream_destroy(cubeb_stream * stm);
- if (get_primary_output_samplingrate) {
- *rate = get_primary_output_samplingrate();
- } else {
- /* We don't really know about the type, here, so we just pass music. */
- r = get_output_samplingrate((int *) rate, AUDIO_STREAM_TYPE_MUSIC);
- if (r) {
- dlclose(libmedia);
- return CUBEB_ERROR;
- }
+#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+static int
+opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format,
+ cubeb_stream_params * params)
+{
+ assert(format);
+ assert(params);
+
+ format->formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
+ format->numChannels = params->channels;
+ // sampleRate is in milliHertz
+ format->sampleRate = params->rate * 1000;
+ format->channelMask = params->channels == 1
+ ? SL_SPEAKER_FRONT_CENTER
+ : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_FLOAT32BE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
}
+ return CUBEB_OK;
+}
+#endif
- dlclose(libmedia);
+static int
+opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params)
+{
+ assert(format);
+ assert(params);
- /* Depending on which method we called above, we can get a zero back, yet have
- * a non-error return value, especially if the audio system is not
- * ready/shutting down (i.e. when we can't get our hand on the AudioFlinger
- * thread). */
- if (*rate == 0) {
- return CUBEB_ERROR;
+ format->formatType = SL_DATAFORMAT_PCM;
+ format->numChannels = params->channels;
+ // samplesPerSec is in milliHertz
+ format->samplesPerSec = params->rate * 1000;
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->channelMask = params->channels == 1
+ ? SL_SPEAKER_FRONT_CENTER
+ : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
}
-
return CUBEB_OK;
}
static int
-opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
+opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
{
- /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html
- * We don't want to deal with JNI here (and we don't have Java on b2g anyways),
- * so we just dlopen the library and get the two symbols we need. */
-
- int r;
- void * libmedia;
- size_t (*get_primary_output_frame_count)(void);
- int (*get_output_frame_count)(size_t * frameCount, int streamType);
- uint32_t primary_sampling_rate;
- size_t primary_buffer_size;
+ assert(stm);
+ assert(params);
- r = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate);
+ SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut;
+ lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+ lDataLocatorOut.numBuffers = NBUFS;
- if (r) {
- return CUBEB_ERROR;
+ SLDataFormat_PCM lDataFormat;
+ int r = opensl_set_format(&lDataFormat, params);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR_INVALID_FORMAT;
}
- libmedia = dlopen("libmedia.so", RTLD_LAZY);
- if (!libmedia) {
- return CUBEB_ERROR;
- }
+ /* For now set device rate to params rate. */
+ stm->input_device_rate = params->rate;
+
+ SLDataSink lDataSink;
+ lDataSink.pLocator = &lDataLocatorOut;
+ lDataSink.pFormat = &lDataFormat;
+
+ SLDataLocator_IODevice lDataLocatorIn;
+ lDataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE;
+ lDataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT;
+ lDataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+ lDataLocatorIn.device = NULL;
+
+ SLDataSource lDataSource;
+ lDataSource.pLocator = &lDataLocatorIn;
+ lDataSource.pFormat = NULL;
+
+ const SLInterfaceID lSoundRecorderIIDs[] = {
+ stm->context->SL_IID_RECORD,
+ stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ stm->context->SL_IID_ANDROIDCONFIGURATION};
+
+ const SLboolean lSoundRecorderReqs[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
+ SL_BOOLEAN_TRUE};
+ // create the audio recorder abstract object
+ SLresult res = (*stm->context->eng)
+ ->CreateAudioRecorder(
+ stm->context->eng, &stm->recorderObj, &lDataSource,
+ &lDataSink, NELEMS(lSoundRecorderIIDs),
+ lSoundRecorderIIDs, lSoundRecorderReqs);
+ // Sample rate not supported. Try again with default sample rate!
+ if (res == SL_RESULT_CONTENT_UNSUPPORTED) {
+ if (stm->output_enabled && stm->output_configured_rate != 0) {
+ // Set the same with the player. Since there is no
+ // api for input device this is a safe choice.
+ stm->input_device_rate = stm->output_configured_rate;
+ } else {
+ // The output preferred rate is used for an input only scenario.
+ // The default rate expected to be supported from all android devices.
+ stm->input_device_rate = DEFAULT_SAMPLE_RATE;
+ }
+ lDataFormat.samplesPerSec = stm->input_device_rate * 1000;
+ res = (*stm->context->eng)
+ ->CreateAudioRecorder(stm->context->eng, &stm->recorderObj,
+ &lDataSource, &lDataSink,
+ NELEMS(lSoundRecorderIIDs),
+ lSoundRecorderIIDs, lSoundRecorderReqs);
- /* JB variant */
- /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */
- get_primary_output_frame_count =
- dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv");
- if (!get_primary_output_frame_count) {
- /* ICS variant */
- /* status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) */
- get_output_frame_count =
- dlsym(libmedia, "_ZN7android11AudioSystem19getOutputFrameCountEPii");
- if (!get_output_frame_count) {
- dlclose(libmedia);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to create recorder. Error code: %lu", res);
return CUBEB_ERROR;
}
}
- if (get_primary_output_frame_count) {
- primary_buffer_size = get_primary_output_frame_count();
- } else {
- if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) {
+ if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) {
+ SLAndroidConfigurationItf recorderConfig;
+ res = (*stm->recorderObj)
+ ->GetInterface(stm->recorderObj,
+ stm->context->SL_IID_ANDROIDCONFIGURATION,
+ &recorderConfig);
+
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get the android configuration interface for recorder. "
+ "Error "
+ "code: %lu",
+ res);
return CUBEB_ERROR;
}
- }
- /* To get a fast track in Android's mixer, we need to be at the native
- * samplerate, which is device dependant. Some devices might be able to
- * resample when playing a fast track, but it's pretty rare. */
- *latency_frames = NBUFS * primary_buffer_size;
+ // Voice recognition is the lowest latency, according to the docs. Camcorder
+ // uses a microphone that is in the same direction as the camera.
+ SLint32 streamType = stm->voice_input
+ ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
+ : SL_ANDROID_RECORDING_PRESET_CAMCORDER;
- dlclose(libmedia);
+ res =
+ (*recorderConfig)
+ ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET,
+ &streamType, sizeof(SLint32));
- return CUBEB_OK;
-}
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set the android configuration to VOICE for the recorder. "
+ "Error code: %lu",
+ res);
+ return CUBEB_ERROR;
+ }
+ }
+ // realize the audio recorder
+ res = (*stm->recorderObj)->Realize(stm->recorderObj, SL_BOOLEAN_FALSE);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to realize recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // get the record interface
+ res = (*stm->recorderObj)
+ ->GetInterface(stm->recorderObj, stm->context->SL_IID_RECORD,
+ &stm->recorderItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get recorder interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
-static void
-opensl_destroy(cubeb * ctx)
-{
- if (ctx->outmixObj)
- (*ctx->outmixObj)->Destroy(ctx->outmixObj);
- if (ctx->engObj)
- cubeb_destroy_sles_engine(&ctx->engObj);
- dlclose(ctx->lib);
- dlclose(ctx->libmedia);
- free(ctx);
-}
+ res = (*stm->recorderItf)
+ ->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register recorder marker callback. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
-static void opensl_stream_destroy(cubeb_stream * stm);
+ (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0);
-static int
-opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback, cubeb_state_callback state_callback,
- void * user_ptr)
-{
- cubeb_stream * stm;
+ res = (*stm->recorderItf)
+ ->SetCallbackEventsMask(stm->recorderItf,
+ (SLuint32)SL_RECORDEVENT_HEADATMARKER);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set headatmarker event mask. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // get the simple android buffer queue interface
+ res = (*stm->recorderObj)
+ ->GetInterface(stm->recorderObj,
+ stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &stm->recorderBufferQueueItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get recorder (android) buffer queue interface. Error code: "
+ "%lu",
+ res);
+ return CUBEB_ERROR;
+ }
- assert(ctx);
- assert(!input_stream_params && "not supported");
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ // register callback on record (input) buffer queue
+ slAndroidSimpleBufferQueueCallback rec_callback = recorder_callback;
+ if (stm->output_enabled) {
+ // Register full duplex callback instead.
+ rec_callback = recorder_fullduplex_callback;
+ }
+ res = (*stm->recorderBufferQueueItf)
+ ->RegisterCallback(stm->recorderBufferQueueItf, rec_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register recorder buffer queue callback. Error code: %lu",
+ res);
+ return CUBEB_ERROR;
}
- *stream = NULL;
+ // Calculate length of input buffer according to requested latency
+ stm->input_frame_size = params->channels * sizeof(int16_t);
+ stm->input_buffer_length = (stm->input_frame_size * stm->buffer_size_frames);
- SLDataFormat_PCM format;
+ // Calculate the capacity of input array
+ stm->input_array_capacity = NBUFS;
+ if (stm->output_enabled) {
+ // Full duplex, update capacity to hold 1 sec of data
+ stm->input_array_capacity =
+ 1 * stm->input_device_rate / stm->input_buffer_length;
+ }
+ // Allocate input array
+ stm->input_buffer_array =
+ (void **)calloc(1, sizeof(void *) * stm->input_array_capacity);
+ // Buffering has not started yet.
+ stm->input_buffer_index = -1;
+ // Prepare input buffers
+ for (uint32_t i = 0; i < stm->input_array_capacity; ++i) {
+ stm->input_buffer_array[i] = calloc(1, stm->input_buffer_length);
+ }
- format.formatType = SL_DATAFORMAT_PCM;
- format.numChannels = output_stream_params->channels;
- // samplesPerSec is in milliHertz
- format.samplesPerSec = output_stream_params->rate * 1000;
- format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
- format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
- format.channelMask = output_stream_params->channels == 1 ?
- SL_SPEAKER_FRONT_CENTER :
- SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
-
- switch (output_stream_params->format) {
- case CUBEB_SAMPLE_S16LE:
- format.endianness = SL_BYTEORDER_LITTLEENDIAN;
- break;
- case CUBEB_SAMPLE_S16BE:
- format.endianness = SL_BYTEORDER_BIGENDIAN;
- break;
- default:
- return CUBEB_ERROR_INVALID_FORMAT;
+ // On full duplex allocate input queue and silent buffer
+ if (stm->output_enabled) {
+ stm->input_queue = array_queue_create(stm->input_array_capacity);
+ assert(stm->input_queue);
+ stm->input_silent_buffer = calloc(1, stm->input_buffer_length);
+ assert(stm->input_silent_buffer);
}
- stm = calloc(1, sizeof(*stm));
- assert(stm);
+ // Enqueue buffer to start rolling once recorder started
+ r = opensl_enqueue_recorder(stm, NULL);
+ if (r != CUBEB_OK) {
+ return r;
+ }
- stm->context = ctx;
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
+ LOG("Cubeb stream init recorder success");
+
+ return CUBEB_OK;
+}
- stm->inputrate = output_stream_params->rate;
- stm->latency = latency_frames;
- stm->stream_type = output_stream_params->stream_type;
- stm->framesize = output_stream_params->channels * sizeof(int16_t);
+static int
+opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params)
+{
+ assert(stm);
+ assert(params);
+
+ stm->user_output_rate = params->rate;
+ if (params->format == CUBEB_SAMPLE_S16NE ||
+ params->format == CUBEB_SAMPLE_S16BE) {
+ stm->framesize = params->channels * sizeof(int16_t);
+ } else if (params->format == CUBEB_SAMPLE_FLOAT32NE ||
+ params->format == CUBEB_SAMPLE_FLOAT32BE) {
+ stm->framesize = params->channels * sizeof(float);
+ }
stm->lastPosition = -1;
stm->lastPositionTimeStamp = 0;
stm->lastCompensativePosition = -1;
- int r = pthread_mutex_init(&stm->mutex, NULL);
- assert(r == 0);
+ void * format = NULL;
+ SLuint32 * format_sample_rate = NULL;
+
+#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+ SLAndroidDataFormat_PCM_EX pcm_ext_format;
+ if (get_android_version() >= ANDROID_VERSION_LOLLIPOP) {
+ if (opensl_set_format_ext(&pcm_ext_format, params) != CUBEB_OK) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ format = &pcm_ext_format;
+ format_sample_rate = &pcm_ext_format.sampleRate;
+ }
+#endif
+
+ SLDataFormat_PCM pcm_format;
+ if (!format) {
+ if (opensl_set_format(&pcm_format, params) != CUBEB_OK) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ format = &pcm_format;
+ format_sample_rate = &pcm_format.samplesPerSec;
+ }
SLDataLocator_BufferQueue loc_bufq;
loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
loc_bufq.numBuffers = NBUFS;
SLDataSource source;
source.pLocator = &loc_bufq;
- source.pFormat = &format;
+ source.pFormat = format;
SLDataLocator_OutputMix loc_outmix;
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
- loc_outmix.outputMix = ctx->outmixObj;
+ loc_outmix.outputMix = stm->context->outmixObj;
SLDataSink sink;
sink.pLocator = &loc_outmix;
sink.pFormat = NULL;
#if defined(__ANDROID__)
- const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE,
- ctx->SL_IID_VOLUME,
- ctx->SL_IID_ANDROIDCONFIGURATION};
+ const SLInterfaceID ids[] = {stm->context->SL_IID_BUFFERQUEUE,
+ stm->context->SL_IID_VOLUME,
+ stm->context->SL_IID_ANDROIDCONFIGURATION};
const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
#else
const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME};
@@ -579,130 +1152,182 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
#endif
assert(NELEMS(ids) == NELEMS(req));
- uint32_t preferred_sampling_rate = stm->inputrate;
-#if defined(__ANDROID__)
- if (get_android_version() >= ANDROID_VERSION_MARSHMALLOW) {
- // Reset preferred samping rate to trigger fallback to native sampling rate.
- preferred_sampling_rate = 0;
- if (opensl_get_min_latency(ctx, *output_stream_params, &latency_frames) != CUBEB_OK) {
- // Default to AudioFlinger's advertised fast track latency of 10ms.
- latency_frames = 440;
- }
- stm->latency = latency_frames;
- }
-#endif
-
+ uint32_t preferred_sampling_rate = stm->user_output_rate;
SLresult res = SL_RESULT_CONTENT_UNSUPPORTED;
if (preferred_sampling_rate) {
- res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, &source,
- &sink, NELEMS(ids), ids, req);
+ res = (*stm->context->eng)
+ ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source,
+ &sink, NELEMS(ids), ids, req);
}
// Sample rate not supported? Try again with primary sample rate!
- if (res == SL_RESULT_CONTENT_UNSUPPORTED) {
- if (opensl_get_preferred_sample_rate(ctx, &preferred_sampling_rate)) {
- opensl_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- format.samplesPerSec = preferred_sampling_rate * 1000;
- res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj,
- &source, &sink, NELEMS(ids), ids, req);
+ if (res == SL_RESULT_CONTENT_UNSUPPORTED &&
+ preferred_sampling_rate != DEFAULT_SAMPLE_RATE) {
+ preferred_sampling_rate = DEFAULT_SAMPLE_RATE;
+ *format_sample_rate = preferred_sampling_rate * 1000;
+ res = (*stm->context->eng)
+ ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source,
+ &sink, NELEMS(ids), ids, req);
}
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to create audio player. Error code: %lu", res);
return CUBEB_ERROR;
}
- stm->outputrate = preferred_sampling_rate;
- stm->bytespersec = stm->outputrate * stm->framesize;
- stm->queuebuf_len = stm->framesize * latency_frames / NBUFS;
- // round up to the next multiple of stm->framesize, if needed.
- if (stm->queuebuf_len % stm->framesize) {
- stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize);
- }
-
- cubeb_stream_params params = *output_stream_params;
- params.rate = preferred_sampling_rate;
+ stm->output_configured_rate = preferred_sampling_rate;
+ stm->bytespersec = stm->output_configured_rate * stm->framesize;
+ stm->queuebuf_len = stm->framesize * stm->buffer_size_frames;
- stm->resampler = cubeb_resampler_create(stm, NULL, &params,
- output_stream_params->rate,
- data_callback,
- user_ptr,
- CUBEB_RESAMPLER_QUALITY_DEFAULT);
-
- if (!stm->resampler) {
- opensl_stream_destroy(stm);
- return CUBEB_ERROR;
+ // Calculate the capacity of input array
+ stm->queuebuf_capacity = NBUFS;
+ if (stm->output_enabled) {
+ // Full duplex, update capacity to hold 1 sec of data
+ stm->queuebuf_capacity =
+ 1 * stm->output_configured_rate / stm->queuebuf_len;
}
-
- int i;
- for (i = 0; i < NBUFS; i++) {
- stm->queuebuf[i] = malloc(stm->queuebuf_len);
+ // Allocate input array
+ stm->queuebuf = (void **)calloc(1, sizeof(void *) * stm->queuebuf_capacity);
+ for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) {
+ stm->queuebuf[i] = calloc(1, stm->queuebuf_len);
assert(stm->queuebuf[i]);
}
-#if defined(__ANDROID__)
- SLuint32 stream_type = convert_stream_type_to_sl_stream(output_stream_params->stream_type);
- if (stream_type != 0xFFFFFFFF) {
- SLAndroidConfigurationItf playerConfig;
- res = (*stm->playerObj)->GetInterface(stm->playerObj,
- ctx->SL_IID_ANDROIDCONFIGURATION, &playerConfig);
- res = (*playerConfig)->SetConfiguration(playerConfig,
- SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof(SLint32));
+ SLAndroidConfigurationItf playerConfig = NULL;
+
+ if (get_android_version() >= ANDROID_VERSION_N_MR1) {
+ res = (*stm->playerObj)
+ ->GetInterface(stm->playerObj,
+ stm->context->SL_IID_ANDROIDCONFIGURATION,
+ &playerConfig);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to get Android configuration interface. Error code: %lu",
+ res);
return CUBEB_ERROR;
}
+
+ SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
+ if (stm->voice_output) {
+ streamType = SL_ANDROID_STREAM_VOICE;
+ }
+ res = (*playerConfig)
+ ->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE,
+ &streamType, sizeof(streamType));
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set Android configuration to %d Error code: %lu",
+ streamType, res);
+ }
+
+ SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY;
+ if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) {
+ performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
+ }
+
+ res = (*playerConfig)
+ ->SetConfiguration(playerConfig, SL_ANDROID_KEY_PERFORMANCE_MODE,
+ &performanceMode, sizeof(performanceMode));
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set Android performance mode to %d Error code: %lu. This "
+ "is"
+ " not fatal",
+ performanceMode, res);
+ }
}
-#endif
res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to realize player object. Error code: %lu", res);
return CUBEB_ERROR;
}
- res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play);
+ // There are two ways of getting the audio output latency:
+ // - a configuration value, only available on some devices (notably devices
+ // running FireOS)
+ // - A Java method, that we call using JNI.
+ //
+ // The first method is prefered, if available, because it can account for more
+ // latency causes, and is more precise.
+
+ // Latency has to be queried after the realization of the interface, when
+ // using SL_IID_ANDROIDCONFIGURATION.
+ SLuint32 audioLatency = 0;
+ SLuint32 paramSize = sizeof(SLuint32);
+ // The reported latency is in milliseconds.
+ if (playerConfig) {
+ res = (*playerConfig)
+ ->GetConfiguration(playerConfig,
+ (const SLchar *)"androidGetAudioLatency",
+ &paramSize, &audioLatency);
+ if (res == SL_RESULT_SUCCESS) {
+ LOG("Got playback latency using android configuration extension");
+ stm->output_latency_ms = audioLatency;
+ }
+ }
+ // `playerConfig` is available, but the above failed, or `playerConfig` is not
+ // available. In both cases, we need to acquire the output latency by an other
+ // mean.
+ if ((playerConfig && res != SL_RESULT_SUCCESS) || !playerConfig) {
+ if (cubeb_output_latency_method_is_loaded(
+ stm->context->p_output_latency_function)) {
+ LOG("Got playback latency using JNI");
+ stm->output_latency_ms =
+ cubeb_get_output_latency(stm->context->p_output_latency_function);
+ } else {
+ LOG("No alternate latency querying method loaded, A/V sync will be off.");
+ stm->output_latency_ms = 0;
+ }
+ }
+
+ LOG("Audio output latency: %dms", stm->output_latency_ms);
+
+ res =
+ (*stm->playerObj)
+ ->GetInterface(stm->playerObj, stm->context->SL_IID_PLAY, &stm->play);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to get play interface. Error code: %lu", res);
return CUBEB_ERROR;
}
- res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE,
- &stm->bufq);
+ res = (*stm->playerObj)
+ ->GetInterface(stm->playerObj, stm->context->SL_IID_BUFFERQUEUE,
+ &stm->bufq);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to get bufferqueue interface. Error code: %lu", res);
return CUBEB_ERROR;
}
- res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_VOLUME,
- &stm->volume);
-
+ res = (*stm->playerObj)
+ ->GetInterface(stm->playerObj, stm->context->SL_IID_VOLUME,
+ &stm->volume);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to get volume interface. Error code: %lu", res);
return CUBEB_ERROR;
}
res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to register play callback. Error code: %lu", res);
return CUBEB_ERROR;
}
// Work around wilhelm/AudioTrack badness, bug 1221228
(*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0);
- res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER);
+ res = (*stm->play)
+ ->SetCallbackEventsMask(stm->play,
+ (SLuint32)SL_PLAYEVENT_HEADATMARKER);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to set headatmarker event mask. Error code: %lu", res);
return CUBEB_ERROR;
}
- res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm);
+ slBufferQueueCallback player_callback = bufferqueue_callback;
+ if (stm->input_enabled) {
+ player_callback = player_fullduplex_callback;
+ }
+ res = (*stm->bufq)->RegisterCallback(stm->bufq, player_callback, stm);
if (res != SL_RESULT_SUCCESS) {
- opensl_stream_destroy(stm);
+ LOG("Failed to register bufferqueue callback. Error code: %lu", res);
return CUBEB_ERROR;
}
@@ -711,61 +1336,354 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
// will be consumed and kick off the buffer queue callback.
// Note the duration of a single frame is less than 1ms. We don't bother
// adjusting the playback position.
- uint8_t *buf = stm->queuebuf[stm->queuebuf_idx++];
+ uint8_t * buf = stm->queuebuf[stm->queuebuf_idx++];
memset(buf, 0, stm->framesize);
res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->framesize);
assert(res == SL_RESULT_SUCCESS);
}
- *stream = stm;
+ LOG("Cubeb stream init playback success");
return CUBEB_OK;
}
-static void
-opensl_stream_destroy(cubeb_stream * stm)
+static int
+opensl_validate_stream_param(cubeb_stream_params * stream_params)
{
- if (stm->playerObj)
- (*stm->playerObj)->Destroy(stm->playerObj);
- int i;
- for (i = 0; i < NBUFS; i++) {
- free(stm->queuebuf[i]);
+ if ((stream_params &&
+ (stream_params->channels < 1 || stream_params->channels > 32))) {
+ return CUBEB_ERROR_INVALID_FORMAT;
}
- pthread_mutex_destroy(&stm->mutex);
+ if ((stream_params && (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+ LOG("Loopback is not supported");
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+ return CUBEB_OK;
+}
- cubeb_resampler_destroy(stm->resampler);
+int
+has_pref_set(cubeb_stream_params * input_params,
+ cubeb_stream_params * output_params, cubeb_stream_prefs pref)
+{
+ return (input_params && input_params->prefs & pref) ||
+ (output_params && output_params->prefs & pref);
+}
- free(stm);
+static int
+opensl_stream_init(cubeb * ctx, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ cubeb_stream * stm;
+
+ assert(ctx);
+ if (input_device || output_device) {
+ LOG("Device selection is not supported in Android. The default will be "
+ "used");
+ }
+
+ *stream = NULL;
+
+ int r = opensl_validate_stream_param(output_stream_params);
+ if (r != CUBEB_OK) {
+ LOG("Output stream params not valid");
+ return r;
+ }
+ r = opensl_validate_stream_param(input_stream_params);
+ if (r != CUBEB_OK) {
+ LOG("Input stream params not valid");
+ return r;
+ }
+
+ stm = calloc(1, sizeof(*stm));
+ assert(stm);
+
+ stm->context = ctx;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->buffer_size_frames =
+ latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES;
+ stm->input_enabled = (input_stream_params) ? 1 : 0;
+ stm->output_enabled = (output_stream_params) ? 1 : 0;
+ stm->shutdown = 1;
+ stm->voice_input =
+ has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE);
+ stm->voice_output =
+ has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE);
+
+ LOG("cubeb stream prefs: voice_input: %s voice_output: %s",
+ stm->voice_input ? "true" : "false",
+ stm->voice_output ? "true" : "false");
+
+#ifdef DEBUG
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+ r = pthread_mutex_init(&stm->mutex, &attr);
+#else
+ r = pthread_mutex_init(&stm->mutex, NULL);
+#endif
+ assert(r == 0);
+
+ if (output_stream_params) {
+ LOG("Playback params: Rate %d, channels %d, format %d, latency in frames "
+ "%d.",
+ output_stream_params->rate, output_stream_params->channels,
+ output_stream_params->format, stm->buffer_size_frames);
+ r = opensl_configure_playback(stm, output_stream_params);
+ if (r != CUBEB_OK) {
+ opensl_stream_destroy(stm);
+ return r;
+ }
+ }
+
+ if (input_stream_params) {
+ LOG("Capture params: Rate %d, channels %d, format %d, latency in frames "
+ "%d.",
+ input_stream_params->rate, input_stream_params->channels,
+ input_stream_params->format, stm->buffer_size_frames);
+ r = opensl_configure_capture(stm, input_stream_params);
+ if (r != CUBEB_OK) {
+ opensl_stream_destroy(stm);
+ return r;
+ }
+ }
+
+ /* Configure resampler*/
+ uint32_t target_sample_rate;
+ if (input_stream_params) {
+ target_sample_rate = input_stream_params->rate;
+ } else {
+ assert(output_stream_params);
+ target_sample_rate = output_stream_params->rate;
+ }
+
+ // Use the actual configured rates for input
+ // and output.
+ cubeb_stream_params input_params;
+ if (input_stream_params) {
+ input_params = *input_stream_params;
+ input_params.rate = stm->input_device_rate;
+ }
+ cubeb_stream_params output_params;
+ if (output_stream_params) {
+ output_params = *output_stream_params;
+ output_params.rate = stm->output_configured_rate;
+ }
+
+ stm->resampler = cubeb_resampler_create(
+ stm, input_stream_params ? &input_params : NULL,
+ output_stream_params ? &output_params : NULL, target_sample_rate,
+ data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT);
+ if (!stm->resampler) {
+ LOG("Failed to create resampler");
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ *stream = stm;
+ LOG("Cubeb stream (%p) init success", stm);
+ return CUBEB_OK;
+}
+
+static int
+opensl_start_player(cubeb_stream * stm)
+{
+ assert(stm->playerObj);
+ SLuint32 playerState;
+ (*stm->playerObj)->GetState(stm->playerObj, &playerState);
+ if (playerState == SL_OBJECT_STATE_REALIZED) {
+ SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to start player. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+opensl_start_recorder(cubeb_stream * stm)
+{
+ assert(stm->recorderObj);
+ SLuint32 recorderState;
+ (*stm->recorderObj)->GetState(stm->recorderObj, &recorderState);
+ if (recorderState == SL_OBJECT_STATE_REALIZED) {
+ SLresult res =
+ (*stm->recorderItf)
+ ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to start recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
}
static int
opensl_stream_start(cubeb_stream * stm)
{
- SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING);
- if (res != SL_RESULT_SUCCESS)
- return CUBEB_ERROR;
+ assert(stm);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 0);
+ opensl_set_draining(stm, 0);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (stm->playerObj) {
+ r = opensl_start_player(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_start_recorder(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+ LOG("Cubeb stream (%p) started", stm);
return CUBEB_OK;
}
static int
-opensl_stream_stop(cubeb_stream * stm)
+opensl_stop_player(cubeb_stream * stm)
{
+ assert(stm->playerObj);
+ assert(stm->shutdown || stm->draining);
+
SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
- if (res != SL_RESULT_SUCCESS)
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to stop player. Error code: %lu", res);
return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_stop_recorder(cubeb_stream * stm)
+{
+ assert(stm->recorderObj);
+ assert(stm->shutdown || stm->draining);
+
+ SLresult res = (*stm->recorderItf)
+ ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to stop recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_stream_stop(cubeb_stream * stm)
+{
+ assert(stm);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (stm->playerObj) {
+ r = opensl_stop_player(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_stop_recorder(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+ LOG("Cubeb stream (%p) stopped", stm);
return CUBEB_OK;
}
static int
+opensl_destroy_recorder(cubeb_stream * stm)
+{
+ assert(stm);
+ assert(stm->recorderObj);
+
+ if (stm->recorderBufferQueueItf) {
+ SLresult res =
+ (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to clear recorder buffer queue. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ stm->recorderBufferQueueItf = NULL;
+ for (uint32_t i = 0; i < stm->input_array_capacity; ++i) {
+ free(stm->input_buffer_array[i]);
+ }
+ }
+
+ (*stm->recorderObj)->Destroy(stm->recorderObj);
+ stm->recorderObj = NULL;
+ stm->recorderItf = NULL;
+
+ if (stm->input_queue) {
+ array_queue_destroy(stm->input_queue);
+ }
+ free(stm->input_silent_buffer);
+
+ return CUBEB_OK;
+}
+
+static void
+opensl_stream_destroy(cubeb_stream * stm)
+{
+ assert(stm->draining || stm->shutdown);
+
+ if (stm->playerObj) {
+ (*stm->playerObj)->Destroy(stm->playerObj);
+ stm->playerObj = NULL;
+ stm->play = NULL;
+ stm->bufq = NULL;
+ for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) {
+ free(stm->queuebuf[i]);
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_destroy_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+
+ if (stm->resampler) {
+ cubeb_resampler_destroy(stm->resampler);
+ }
+
+ pthread_mutex_destroy(&stm->mutex);
+
+ LOG("Cubeb stream (%p) destroyed", stm);
+ free(stm);
+}
+
+static int
opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
{
SLmillisecond msec;
- uint64_t samplerate;
- SLresult res;
- int r;
- uint32_t mixer_latency;
uint32_t compensation_msec = 0;
+ SLresult res;
res = (*stm->play)->GetPosition(stm->play, &msec);
if (res != SL_RESULT_SUCCESS)
@@ -773,61 +1691,53 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
- if(stm->lastPosition == msec) {
+ if (stm->lastPosition == msec) {
compensation_msec =
- (t.tv_sec*1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / 1000000;
+ (t.tv_sec * 1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) /
+ 1000000;
} else {
- stm->lastPositionTimeStamp = t.tv_sec*1000000000LL + t.tv_nsec;
+ stm->lastPositionTimeStamp = t.tv_sec * 1000000000LL + t.tv_nsec;
stm->lastPosition = msec;
}
- samplerate = stm->inputrate;
-
- r = stm->context->get_output_latency(&mixer_latency, stm->stream_type);
- if (r) {
- return CUBEB_ERROR;
- }
+ uint64_t samplerate = stm->user_output_rate;
+ uint32_t output_latency = stm->output_latency_ms;
pthread_mutex_lock(&stm->mutex);
- int64_t maximum_position = stm->written * (int64_t)stm->inputrate / stm->outputrate;
+ int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate /
+ stm->output_configured_rate;
pthread_mutex_unlock(&stm->mutex);
assert(maximum_position >= 0);
- if (msec > mixer_latency) {
+ if (msec > output_latency) {
int64_t unadjusted_position;
if (stm->lastCompensativePosition > msec + compensation_msec) {
// Over compensation, use lastCompensativePosition.
unadjusted_position =
- samplerate * (stm->lastCompensativePosition - mixer_latency) / 1000;
+ samplerate * (stm->lastCompensativePosition - output_latency) / 1000;
} else {
unadjusted_position =
- samplerate * (msec - mixer_latency + compensation_msec) / 1000;
+ samplerate * (msec - output_latency + compensation_msec) / 1000;
stm->lastCompensativePosition = msec + compensation_msec;
}
- *position = unadjusted_position < maximum_position ?
- unadjusted_position : maximum_position;
+ *position = unadjusted_position < maximum_position ? unadjusted_position
+ : maximum_position;
} else {
*position = 0;
}
return CUBEB_OK;
}
-int
+static int
opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
- int r;
- uint32_t mixer_latency; // The latency returned by AudioFlinger is in ms.
-
- /* audio_stream_type_t is an int, so this is okay. */
- r = stm->context->get_output_latency(&mixer_latency, stm->stream_type);
- if (r) {
- return CUBEB_ERROR;
- }
+ assert(stm);
+ assert(latency);
- *latency = stm->latency * stm->inputrate / 1000 + // OpenSL latency
- mixer_latency * stm->inputrate / 1000; // AudioFlinger latency
+ uint32_t stream_latency_frames =
+ stm->user_output_rate * stm->output_latency_ms / 1000;
- return CUBEB_OK;
+ return stream_latency_frames + cubeb_resampler_latency(stm->resampler);
}
int
@@ -862,23 +1772,24 @@ opensl_stream_set_volume(cubeb_stream * stm, float volume)
}
static struct cubeb_ops const opensl_ops = {
- .init = opensl_init,
- .get_backend_id = opensl_get_backend_id,
- .get_max_channel_count = opensl_get_max_channel_count,
- .get_min_latency = opensl_get_min_latency,
- .get_preferred_sample_rate = opensl_get_preferred_sample_rate,
- .enumerate_devices = NULL,
- .destroy = opensl_destroy,
- .stream_init = opensl_stream_init,
- .stream_destroy = opensl_stream_destroy,
- .stream_start = opensl_stream_start,
- .stream_stop = opensl_stream_stop,
- .stream_get_position = opensl_stream_get_position,
- .stream_get_latency = opensl_stream_get_latency,
- .stream_set_volume = opensl_stream_set_volume,
- .stream_set_panning = NULL,
- .stream_get_current_device = NULL,
- .stream_device_destroy = NULL,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = NULL
-};
+ .init = opensl_init,
+ .get_backend_id = opensl_get_backend_id,
+ .get_max_channel_count = opensl_get_max_channel_count,
+ .get_min_latency = NULL,
+ .get_preferred_sample_rate = NULL,
+ .enumerate_devices = NULL,
+ .device_collection_destroy = NULL,
+ .destroy = opensl_destroy,
+ .stream_init = opensl_stream_init,
+ .stream_destroy = opensl_stream_destroy,
+ .stream_start = opensl_stream_start,
+ .stream_stop = opensl_stream_stop,
+ .stream_get_position = opensl_stream_get_position,
+ .stream_get_latency = opensl_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = opensl_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/media/libcubeb/src/cubeb_oss.c b/media/libcubeb/src/cubeb_oss.c
new file mode 100644
index 0000000000..083c37ffeb
--- /dev/null
+++ b/media/libcubeb/src/cubeb_oss.c
@@ -0,0 +1,1329 @@
+/*
+ * Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
+ * Copyright © 2020 Ka Ho Ng <khng300@gmail.com>
+ * Copyright © 2020 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Ka Ho Ng
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include "cubeb_strings.h"
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* Supported well by most hardware. */
+#ifndef OSS_PREFER_RATE
+#define OSS_PREFER_RATE (48000)
+#endif
+
+/* Standard acceptable minimum. */
+#ifndef OSS_LATENCY_MS
+#define OSS_LATENCY_MS (8)
+#endif
+
+#ifndef OSS_NFRAGS
+#define OSS_NFRAGS (4)
+#endif
+
+#ifndef OSS_DEFAULT_DEVICE
+#define OSS_DEFAULT_DEVICE "/dev/dsp"
+#endif
+
+#ifndef OSS_DEFAULT_MIXER
+#define OSS_DEFAULT_MIXER "/dev/mixer"
+#endif
+
+#define ENV_AUDIO_DEVICE "AUDIO_DEVICE"
+
+#ifndef OSS_MAX_CHANNELS
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+/*
+ * The current maximum number of channels supported
+ * on FreeBSD is 8.
+ *
+ * Reference: FreeBSD 12.1-RELEASE
+ */
+#define OSS_MAX_CHANNELS (8)
+#elif defined(__sun__)
+/*
+ * The current maximum number of channels supported
+ * on Illumos is 16.
+ *
+ * Reference: PSARC 2008/318
+ */
+#define OSS_MAX_CHANNELS (16)
+#else
+#define OSS_MAX_CHANNELS (2)
+#endif
+#endif
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+#define SNDSTAT_BEGIN_STR "Installed devices:"
+#define SNDSTAT_USER_BEGIN_STR "Installed devices from userspace:"
+#define SNDSTAT_FV_BEGIN_STR "File Versions:"
+#endif
+
+static struct cubeb_ops const oss_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+
+ /* Our intern string store */
+ pthread_mutex_t mutex; /* protects devid_strs */
+ cubeb_strings * devid_strs;
+};
+
+struct oss_stream {
+ oss_devnode_t name;
+ int fd;
+ void * buf;
+
+ struct stream_info {
+ int channels;
+ int sample_rate;
+ int fmt;
+ int precision;
+ } info;
+
+ unsigned int frame_size; /* precision in bytes * channels */
+ bool floating;
+};
+
+struct cubeb_stream {
+ struct cubeb * context;
+ void * user_ptr;
+ pthread_t thread;
+ bool doorbell; /* (m) */
+ pthread_cond_t doorbell_cv; /* (m) */
+ pthread_cond_t stopped_cv; /* (m) */
+ pthread_mutex_t mtx; /* Members protected by this should be marked (m) */
+ bool thread_created; /* (m) */
+ bool running; /* (m) */
+ bool destroying; /* (m) */
+ cubeb_state state; /* (m) */
+ float volume /* (m) */;
+ struct oss_stream play;
+ struct oss_stream record;
+ cubeb_data_callback data_cb;
+ cubeb_state_callback state_cb;
+ uint64_t frames_written /* (m) */;
+ unsigned int nfr; /* Number of frames allocated */
+ unsigned int nfrags;
+ unsigned int bufframes;
+};
+
+static char const *
+oss_cubeb_devid_intern(cubeb * context, char const * devid)
+{
+ char const * is;
+ pthread_mutex_lock(&context->mutex);
+ is = cubeb_strings_intern(context->devid_strs, devid);
+ pthread_mutex_unlock(&context->mutex);
+ return is;
+}
+
+int
+oss_init(cubeb ** context, char const * context_name)
+{
+ cubeb * c;
+
+ (void)context_name;
+ if ((c = calloc(1, sizeof(cubeb))) == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ if (cubeb_strings_init(&c->devid_strs) == CUBEB_ERROR) {
+ goto fail;
+ }
+
+ if (pthread_mutex_init(&c->mutex, NULL) != 0) {
+ goto fail;
+ }
+
+ c->ops = &oss_ops;
+ *context = c;
+ return CUBEB_OK;
+
+fail:
+ cubeb_strings_destroy(c->devid_strs);
+ free(c);
+ return CUBEB_ERROR;
+}
+
+static void
+oss_destroy(cubeb * context)
+{
+ pthread_mutex_destroy(&context->mutex);
+ cubeb_strings_destroy(context->devid_strs);
+ free(context);
+}
+
+static char const *
+oss_get_backend_id(cubeb * context)
+{
+ return "oss";
+}
+
+static int
+oss_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
+{
+ (void)context;
+
+ *rate = OSS_PREFER_RATE;
+ return CUBEB_OK;
+}
+
+static int
+oss_get_max_channel_count(cubeb * context, uint32_t * max_channels)
+{
+ (void)context;
+
+ *max_channels = OSS_MAX_CHANNELS;
+ return CUBEB_OK;
+}
+
+static int
+oss_get_min_latency(cubeb * context, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ (void)context;
+
+ *latency_frames = (OSS_LATENCY_MS * params.rate) / 1000;
+ return CUBEB_OK;
+}
+
+static void
+oss_free_cubeb_device_info_strings(cubeb_device_info * cdi)
+{
+ free((char *)cdi->device_id);
+ free((char *)cdi->friendly_name);
+ free((char *)cdi->group_id);
+ cdi->device_id = NULL;
+ cdi->friendly_name = NULL;
+ cdi->group_id = NULL;
+}
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+/*
+ * Check if the specified DSP is okay for the purpose specified
+ * in type. Here type can only specify one operation each time
+ * this helper is called.
+ *
+ * Return 0 if OK, otherwise 1.
+ */
+static int
+oss_probe_open(const char * dsppath, cubeb_device_type type, int * fdp,
+ oss_audioinfo * resai)
+{
+ oss_audioinfo ai;
+ int error;
+ int oflags = (type == CUBEB_DEVICE_TYPE_INPUT) ? O_RDONLY : O_WRONLY;
+ int dspfd = open(dsppath, oflags);
+ if (dspfd == -1)
+ return 1;
+
+ ai.dev = -1;
+ error = ioctl(dspfd, SNDCTL_AUDIOINFO, &ai);
+ if (error < 0) {
+ close(dspfd);
+ return 1;
+ }
+
+ if (resai)
+ *resai = ai;
+ if (fdp)
+ *fdp = dspfd;
+ else
+ close(dspfd);
+ return 0;
+}
+
+struct sndstat_info {
+ oss_devnode_t devname;
+ const char * desc;
+ cubeb_device_type type;
+ int preferred;
+};
+
+static int
+oss_sndstat_line_parse(char * line, int is_ud, struct sndstat_info * sinfo)
+{
+ char *matchptr = line, *n = NULL;
+ struct sndstat_info res;
+
+ memset(&res, 0, sizeof(res));
+
+ n = strchr(matchptr, ':');
+ if (n == NULL)
+ goto fail;
+ if (is_ud == 0) {
+ unsigned int devunit;
+
+ if (sscanf(matchptr, "pcm%u: ", &devunit) < 1)
+ goto fail;
+
+ if (snprintf(res.devname, sizeof(res.devname), "/dev/dsp%u", devunit) < 1)
+ goto fail;
+ } else {
+ if (n - matchptr >= (ssize_t)(sizeof(res.devname) - strlen("/dev/")))
+ goto fail;
+
+ strlcpy(res.devname, "/dev/", sizeof(res.devname));
+ strncat(res.devname, matchptr, n - matchptr);
+ }
+ matchptr = n + 1;
+
+ n = strchr(matchptr, '<');
+ if (n == NULL)
+ goto fail;
+ matchptr = n + 1;
+ n = strrchr(matchptr, '>');
+ if (n == NULL)
+ goto fail;
+ *n = 0;
+ res.desc = matchptr;
+ matchptr = n + 1;
+
+ n = strchr(matchptr, '(');
+ if (n == NULL)
+ goto fail;
+ matchptr = n + 1;
+ n = strrchr(matchptr, ')');
+ if (n == NULL)
+ goto fail;
+ *n = 0;
+ if (!isdigit(matchptr[0])) {
+ if (strstr(matchptr, "play") != NULL)
+ res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
+ if (strstr(matchptr, "rec") != NULL)
+ res.type |= CUBEB_DEVICE_TYPE_INPUT;
+ } else {
+ int p, r;
+ if (sscanf(matchptr, "%dp:%*dv/%dr:%*dv", &p, &r) != 2)
+ goto fail;
+ if (p > 0)
+ res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
+ if (r > 0)
+ res.type |= CUBEB_DEVICE_TYPE_INPUT;
+ }
+ matchptr = n + 1;
+ if (strstr(matchptr, "default") != NULL)
+ res.preferred = 1;
+
+ *sinfo = res;
+ return 0;
+
+fail:
+ return 1;
+}
+
+/*
+ * XXX: On FreeBSD we have to rely on SNDCTL_CARDINFO to get all
+ * the usable audio devices currently, as SNDCTL_AUDIOINFO will
+ * never return directly usable audio device nodes.
+ */
+static int
+oss_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ cubeb_device_info * devinfop = NULL;
+ char * line = NULL;
+ size_t linecap = 0;
+ FILE * sndstatfp = NULL;
+ int collection_cnt = 0;
+ int is_ud = 0;
+ int skipall = 0;
+
+ devinfop = calloc(1, sizeof(cubeb_device_info));
+ if (devinfop == NULL)
+ goto fail;
+
+ sndstatfp = fopen("/dev/sndstat", "r");
+ if (sndstatfp == NULL)
+ goto fail;
+ while (getline(&line, &linecap, sndstatfp) > 0) {
+ const char * devid = NULL;
+ struct sndstat_info sinfo;
+ oss_audioinfo ai;
+
+ if (!strncmp(line, SNDSTAT_FV_BEGIN_STR, strlen(SNDSTAT_FV_BEGIN_STR))) {
+ skipall = 1;
+ continue;
+ }
+ if (!strncmp(line, SNDSTAT_BEGIN_STR, strlen(SNDSTAT_BEGIN_STR))) {
+ is_ud = 0;
+ skipall = 0;
+ continue;
+ }
+ if (!strncmp(line, SNDSTAT_USER_BEGIN_STR,
+ strlen(SNDSTAT_USER_BEGIN_STR))) {
+ is_ud = 1;
+ skipall = 0;
+ continue;
+ }
+ if (skipall || isblank(line[0]))
+ continue;
+
+ if (oss_sndstat_line_parse(line, is_ud, &sinfo))
+ continue;
+
+ devinfop[collection_cnt].type = 0;
+ switch (sinfo.type) {
+ case CUBEB_DEVICE_TYPE_INPUT:
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT)
+ continue;
+ break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ if (type & CUBEB_DEVICE_TYPE_INPUT)
+ continue;
+ break;
+ case 0:
+ continue;
+ }
+
+ if (oss_probe_open(sinfo.devname, type, NULL, &ai))
+ continue;
+
+ devid = oss_cubeb_devid_intern(context, sinfo.devname);
+ if (devid == NULL)
+ continue;
+
+ devinfop[collection_cnt].device_id = strdup(sinfo.devname);
+ asprintf((char **)&devinfop[collection_cnt].friendly_name, "%s: %s",
+ sinfo.devname, sinfo.desc);
+ devinfop[collection_cnt].group_id = strdup(sinfo.devname);
+ devinfop[collection_cnt].vendor_name = NULL;
+ if (devinfop[collection_cnt].device_id == NULL ||
+ devinfop[collection_cnt].friendly_name == NULL ||
+ devinfop[collection_cnt].group_id == NULL) {
+ oss_free_cubeb_device_info_strings(&devinfop[collection_cnt]);
+ continue;
+ }
+
+ devinfop[collection_cnt].type = type;
+ devinfop[collection_cnt].devid = devid;
+ devinfop[collection_cnt].state = CUBEB_DEVICE_STATE_ENABLED;
+ devinfop[collection_cnt].preferred =
+ (sinfo.preferred) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
+ devinfop[collection_cnt].format = CUBEB_DEVICE_FMT_S16NE;
+ devinfop[collection_cnt].default_format = CUBEB_DEVICE_FMT_S16NE;
+ devinfop[collection_cnt].max_channels = ai.max_channels;
+ devinfop[collection_cnt].default_rate = OSS_PREFER_RATE;
+ devinfop[collection_cnt].max_rate = ai.max_rate;
+ devinfop[collection_cnt].min_rate = ai.min_rate;
+ devinfop[collection_cnt].latency_lo = 0;
+ devinfop[collection_cnt].latency_hi = 0;
+
+ collection_cnt++;
+
+ void * newp =
+ reallocarray(devinfop, collection_cnt + 1, sizeof(cubeb_device_info));
+ if (newp == NULL)
+ goto fail;
+ devinfop = newp;
+ }
+
+ free(line);
+ fclose(sndstatfp);
+
+ collection->count = collection_cnt;
+ collection->device = devinfop;
+
+ return CUBEB_OK;
+
+fail:
+ free(line);
+ if (sndstatfp)
+ fclose(sndstatfp);
+ free(devinfop);
+ return CUBEB_ERROR;
+}
+
+#else
+
+static int
+oss_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ oss_sysinfo si;
+ int error, i;
+ cubeb_device_info * devinfop = NULL;
+ int collection_cnt = 0;
+ int mixer_fd = -1;
+
+ mixer_fd = open(OSS_DEFAULT_MIXER, O_RDWR);
+ if (mixer_fd == -1) {
+ LOG("Failed to open mixer %s. errno: %d", OSS_DEFAULT_MIXER, errno);
+ return CUBEB_ERROR;
+ }
+
+ error = ioctl(mixer_fd, SNDCTL_SYSINFO, &si);
+ if (error) {
+ LOG("Failed to run SNDCTL_SYSINFO on mixer %s. errno: %d",
+ OSS_DEFAULT_MIXER, errno);
+ goto fail;
+ }
+
+ devinfop = calloc(si.numaudios, sizeof(cubeb_device_info));
+ if (devinfop == NULL)
+ goto fail;
+
+ collection->count = 0;
+ for (i = 0; i < si.numaudios; i++) {
+ oss_audioinfo ai;
+ cubeb_device_info cdi = {0};
+ const char * devid = NULL;
+
+ ai.dev = i;
+ error = ioctl(mixer_fd, SNDCTL_AUDIOINFO, &ai);
+ if (error)
+ goto fail;
+
+ assert(ai.dev < si.numaudios);
+ if (!ai.enabled)
+ continue;
+
+ cdi.type = 0;
+ switch (ai.caps & DSP_CAP_DUPLEX) {
+ case DSP_CAP_INPUT:
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT)
+ continue;
+ break;
+ case DSP_CAP_OUTPUT:
+ if (type & CUBEB_DEVICE_TYPE_INPUT)
+ continue;
+ break;
+ case 0:
+ continue;
+ }
+ cdi.type = type;
+
+ devid = oss_cubeb_devid_intern(context, ai.devnode);
+ cdi.device_id = strdup(ai.name);
+ cdi.friendly_name = strdup(ai.name);
+ cdi.group_id = strdup(ai.name);
+ if (devid == NULL || cdi.device_id == NULL || cdi.friendly_name == NULL ||
+ cdi.group_id == NULL) {
+ oss_free_cubeb_device_info_strings(&cdi);
+ continue;
+ }
+
+ cdi.devid = devid;
+ cdi.vendor_name = NULL;
+ cdi.state = CUBEB_DEVICE_STATE_ENABLED;
+ cdi.preferred = CUBEB_DEVICE_PREF_NONE;
+ cdi.format = CUBEB_DEVICE_FMT_S16NE;
+ cdi.default_format = CUBEB_DEVICE_FMT_S16NE;
+ cdi.max_channels = ai.max_channels;
+ cdi.default_rate = OSS_PREFER_RATE;
+ cdi.max_rate = ai.max_rate;
+ cdi.min_rate = ai.min_rate;
+ cdi.latency_lo = 0;
+ cdi.latency_hi = 0;
+
+ devinfop[collection_cnt++] = cdi;
+ }
+
+ collection->count = collection_cnt;
+ collection->device = devinfop;
+
+ if (mixer_fd != -1)
+ close(mixer_fd);
+ return CUBEB_OK;
+
+fail:
+ if (mixer_fd != -1)
+ close(mixer_fd);
+ free(devinfop);
+ return CUBEB_ERROR;
+}
+
+#endif
+
+static int
+oss_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ size_t i;
+ for (i = 0; i < collection->count; i++) {
+ oss_free_cubeb_device_info_strings(&collection->device[i]);
+ }
+ free(collection->device);
+ collection->device = NULL;
+ collection->count = 0;
+ return 0;
+}
+
+static unsigned int
+oss_chn_from_cubeb(cubeb_channel chn)
+{
+ switch (chn) {
+ case CHANNEL_FRONT_LEFT:
+ return CHID_L;
+ case CHANNEL_FRONT_RIGHT:
+ return CHID_R;
+ case CHANNEL_FRONT_CENTER:
+ return CHID_C;
+ case CHANNEL_LOW_FREQUENCY:
+ return CHID_LFE;
+ case CHANNEL_BACK_LEFT:
+ return CHID_LR;
+ case CHANNEL_BACK_RIGHT:
+ return CHID_RR;
+ case CHANNEL_SIDE_LEFT:
+ return CHID_LS;
+ case CHANNEL_SIDE_RIGHT:
+ return CHID_RS;
+ default:
+ return CHID_UNDEF;
+ }
+}
+
+static unsigned long long
+oss_cubeb_layout_to_chnorder(cubeb_channel_layout layout)
+{
+ unsigned int i, nchns = 0;
+ unsigned long long chnorder = 0;
+
+ for (i = 0; layout; i++, layout >>= 1) {
+ unsigned long long chid = oss_chn_from_cubeb((layout & 1) << i);
+ if (chid == CHID_UNDEF)
+ continue;
+
+ chnorder |= (chid & 0xf) << nchns * 4;
+ nchns++;
+ }
+
+ return chnorder;
+}
+
+static int
+oss_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
+ struct stream_info * sinfo)
+{
+ unsigned long long chnorder;
+
+ sinfo->channels = params->channels;
+ sinfo->sample_rate = params->rate;
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ sinfo->fmt = AFMT_S16_LE;
+ sinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ sinfo->fmt = AFMT_S16_BE;
+ sinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ sinfo->fmt = AFMT_S32_NE;
+ sinfo->precision = 32;
+ break;
+ default:
+ LOG("Unsupported format");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ if (ioctl(fd, SNDCTL_DSP_CHANNELS, &sinfo->channels) == -1) {
+ return CUBEB_ERROR;
+ }
+ if (ioctl(fd, SNDCTL_DSP_SETFMT, &sinfo->fmt) == -1) {
+ return CUBEB_ERROR;
+ }
+ if (ioctl(fd, SNDCTL_DSP_SPEED, &sinfo->sample_rate) == -1) {
+ return CUBEB_ERROR;
+ }
+ /* Mono layout is an exception */
+ if (params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ params->layout != CUBEB_LAYOUT_MONO) {
+ chnorder = oss_cubeb_layout_to_chnorder(params->layout);
+ if (ioctl(fd, SNDCTL_DSP_SET_CHNORDER, &chnorder) == -1)
+ LOG("Non-fatal error %d occured when setting channel order.", errno);
+ }
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_stop(cubeb_stream * s)
+{
+ pthread_mutex_lock(&s->mtx);
+ if (s->thread_created && s->running) {
+ s->running = false;
+ s->doorbell = false;
+ pthread_cond_wait(&s->stopped_cv, &s->mtx);
+ }
+ if (s->state != CUBEB_STATE_STOPPED) {
+ s->state = CUBEB_STATE_STOPPED;
+ pthread_mutex_unlock(&s->mtx);
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_STOPPED);
+ } else {
+ pthread_mutex_unlock(&s->mtx);
+ }
+ return CUBEB_OK;
+}
+
+static void
+oss_stream_destroy(cubeb_stream * s)
+{
+ pthread_mutex_lock(&s->mtx);
+ if (s->thread_created) {
+ s->destroying = true;
+ s->doorbell = true;
+ pthread_cond_signal(&s->doorbell_cv);
+ }
+ pthread_mutex_unlock(&s->mtx);
+ pthread_join(s->thread, NULL);
+
+ pthread_cond_destroy(&s->doorbell_cv);
+ pthread_cond_destroy(&s->stopped_cv);
+ pthread_mutex_destroy(&s->mtx);
+ if (s->play.fd != -1) {
+ close(s->play.fd);
+ }
+ if (s->record.fd != -1) {
+ close(s->record.fd);
+ }
+ free(s->play.buf);
+ free(s->record.buf);
+ free(s);
+}
+
+static void
+oss_float_to_linear32(void * buf, unsigned sample_count, float vol)
+{
+ float * in = buf;
+ int32_t * out = buf;
+ int32_t * tail = out + sample_count;
+
+ while (out < tail) {
+ int64_t f = *(in++) * vol * 0x80000000LL;
+ if (f < -INT32_MAX)
+ f = -INT32_MAX;
+ else if (f > INT32_MAX)
+ f = INT32_MAX;
+ *(out++) = f;
+ }
+}
+
+static void
+oss_linear32_to_float(void * buf, unsigned sample_count)
+{
+ int32_t * in = buf;
+ float * out = buf;
+ float * tail = out + sample_count;
+
+ while (out < tail) {
+ *(out++) = (1.0 / 0x80000000LL) * *(in++);
+ }
+}
+
+static void
+oss_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
+{
+ unsigned i;
+ int32_t multiplier = vol * 0x8000;
+
+ for (i = 0; i < sample_count; ++i) {
+ buf[i] = (buf[i] * multiplier) >> 15;
+ }
+}
+
+static int
+oss_get_rec_frames(cubeb_stream * s, unsigned int nframes)
+{
+ size_t rem = nframes * s->record.frame_size;
+ size_t read_ofs = 0;
+ while (rem > 0) {
+ ssize_t n;
+ if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, rem)) <
+ 0) {
+ if (errno == EINTR)
+ continue;
+ return CUBEB_ERROR;
+ }
+ read_ofs += n;
+ rem -= n;
+ }
+ return 0;
+}
+
+static int
+oss_put_play_frames(cubeb_stream * s, unsigned int nframes)
+{
+ size_t rem = nframes * s->play.frame_size;
+ size_t write_ofs = 0;
+ while (rem > 0) {
+ ssize_t n;
+ if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, rem)) < 0) {
+ if (errno == EINTR)
+ continue;
+ return CUBEB_ERROR;
+ }
+ pthread_mutex_lock(&s->mtx);
+ s->frames_written += n / s->play.frame_size;
+ pthread_mutex_unlock(&s->mtx);
+ write_ofs += n;
+ rem -= n;
+ }
+ return 0;
+}
+
+static int
+oss_wait_playfd_for_space(cubeb_stream * s)
+{
+ struct pollfd pfd;
+
+ pfd.events = POLLOUT | POLLHUP;
+ pfd.revents = 0;
+ pfd.fd = s->play.fd;
+
+ if (poll(&pfd, 1, 2000) == -1) {
+ return CUBEB_ERROR;
+ }
+
+ if (pfd.revents & POLLHUP) {
+ return CUBEB_ERROR;
+ }
+ return 0;
+}
+
+static int
+oss_wait_recfd_for_space(cubeb_stream * s)
+{
+ struct pollfd pfd;
+
+ pfd.events = POLLIN | POLLHUP;
+ pfd.revents = 0;
+ pfd.fd = s->record.fd;
+
+ if (poll(&pfd, 1, 2000) == -1) {
+ return CUBEB_ERROR;
+ }
+
+ if (pfd.revents & POLLHUP) {
+ return CUBEB_ERROR;
+ }
+ return 0;
+}
+
+/* 1 - Stopped by cubeb_stream_stop, otherwise 0 */
+static int
+oss_audio_loop(cubeb_stream * s, cubeb_state * new_state)
+{
+ cubeb_state state = CUBEB_STATE_STOPPED;
+ int trig = 0, drain = 0;
+ const bool play_on = s->play.fd != -1, record_on = s->record.fd != -1;
+ long nfr = 0;
+
+ if (record_on) {
+ if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) {
+ LOG("Error %d occured when setting trigger on record fd", errno);
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ trig |= PCM_ENABLE_INPUT;
+ memset(s->record.buf, 0, s->bufframes * s->record.frame_size);
+
+ if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) {
+ LOG("Error %d occured when setting trigger on record fd", errno);
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ }
+
+ if (!play_on && !record_on) {
+ /*
+ * Stop here if the stream is not play & record stream,
+ * play-only stream or record-only stream
+ */
+
+ goto breakdown;
+ }
+
+ while (1) {
+ pthread_mutex_lock(&s->mtx);
+ if (!s->running || s->destroying) {
+ pthread_mutex_unlock(&s->mtx);
+ break;
+ }
+ pthread_mutex_unlock(&s->mtx);
+
+ long got = 0;
+ if (nfr > 0) {
+ if (record_on) {
+ if (oss_get_rec_frames(s, nfr) == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ if (s->record.floating) {
+ oss_linear32_to_float(s->record.buf, s->record.info.channels * nfr);
+ }
+ }
+ got = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf, nfr);
+ if (got == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ if (got < nfr) {
+ if (s->play.fd != -1) {
+ drain = 1;
+ } else {
+ /*
+ * This is a record-only stream and number of frames
+ * returned from data_cb() is smaller than number
+ * of frames required to read. Stop here.
+ */
+ state = CUBEB_STATE_STOPPED;
+ goto breakdown;
+ }
+ }
+
+ if (got > 0 && play_on) {
+ float vol;
+
+ pthread_mutex_lock(&s->mtx);
+ vol = s->volume;
+ pthread_mutex_unlock(&s->mtx);
+
+ if (s->play.floating) {
+ oss_float_to_linear32(s->play.buf, s->play.info.channels * got, vol);
+ } else {
+ oss_linear16_set_vol((int16_t *)s->play.buf,
+ s->play.info.channels * got, vol);
+ }
+ if (oss_put_play_frames(s, got) == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ }
+ if (drain) {
+ state = CUBEB_STATE_DRAINED;
+ goto breakdown;
+ }
+ }
+
+ nfr = s->bufframes;
+
+ if (record_on) {
+ long mfr;
+
+ if (oss_wait_recfd_for_space(s) != 0) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ audio_buf_info bi;
+ if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi) == -1) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ mfr = (bi.fragsize * bi.fragments) / s->record.frame_size;
+ if (nfr > mfr)
+ nfr = mfr;
+ }
+
+ if (play_on) {
+ long mfr;
+
+ if (oss_wait_playfd_for_space(s) != 0) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ audio_buf_info bi;
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ mfr = (bi.fragsize * bi.fragments) / s->play.frame_size;
+ if (nfr > mfr)
+ nfr = mfr;
+ }
+ }
+
+ return 1;
+
+breakdown:
+ pthread_mutex_lock(&s->mtx);
+ *new_state = s->state = state;
+ s->running = false;
+ pthread_mutex_unlock(&s->mtx);
+ return 0;
+}
+
+static void *
+oss_io_routine(void * arg)
+{
+ cubeb_stream * s = arg;
+ cubeb_state new_state;
+ int stopped;
+
+ do {
+ pthread_mutex_lock(&s->mtx);
+ if (s->destroying) {
+ pthread_mutex_unlock(&s->mtx);
+ break;
+ }
+ pthread_mutex_unlock(&s->mtx);
+
+ stopped = oss_audio_loop(s, &new_state);
+ if (s->record.fd != -1)
+ ioctl(s->record.fd, SNDCTL_DSP_HALT_INPUT, NULL);
+ if (!stopped)
+ s->state_cb(s, s->user_ptr, new_state);
+
+ pthread_mutex_lock(&s->mtx);
+ pthread_cond_signal(&s->stopped_cv);
+ if (s->destroying) {
+ pthread_mutex_unlock(&s->mtx);
+ break;
+ }
+ while (!s->doorbell) {
+ pthread_cond_wait(&s->doorbell_cv, &s->mtx);
+ }
+ s->doorbell = false;
+ pthread_mutex_unlock(&s->mtx);
+ } while (1);
+
+ pthread_mutex_lock(&s->mtx);
+ s->thread_created = false;
+ pthread_mutex_unlock(&s->mtx);
+ return NULL;
+}
+
+static inline int
+oss_calc_frag_shift(unsigned int frames, unsigned int frame_size)
+{
+ int n = 4;
+ int blksize = (frames * frame_size + OSS_NFRAGS - 1) / OSS_NFRAGS;
+ while ((1 << n) < blksize)
+ n++;
+ return n;
+}
+
+static inline int
+oss_get_frag_params(unsigned int shift)
+{
+ return (OSS_NFRAGS << 16) | shift;
+}
+
+static int
+oss_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int ret = CUBEB_OK;
+ unsigned int playnfr = 0, recnfr = 0;
+ cubeb_stream * s = NULL;
+ const char * defdsp;
+
+ if (!(defdsp = getenv(ENV_AUDIO_DEVICE)) || *defdsp == '\0')
+ defdsp = OSS_DEFAULT_DEVICE;
+
+ (void)stream_name;
+ if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ s->state = CUBEB_STATE_STOPPED;
+ s->record.fd = s->play.fd = -1;
+ s->nfr = latency_frames;
+ if (input_device != NULL) {
+ strlcpy(s->record.name, input_device, sizeof(s->record.name));
+ } else {
+ strlcpy(s->record.name, defdsp, sizeof(s->record.name));
+ }
+ if (output_device != NULL) {
+ strlcpy(s->play.name, output_device, sizeof(s->play.name));
+ } else {
+ strlcpy(s->play.name, defdsp, sizeof(s->play.name));
+ }
+ if (input_stream_params != NULL) {
+ unsigned int nb_channels;
+ if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ nb_channels = cubeb_channel_layout_nb_channels(input_stream_params->layout);
+ if (input_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ nb_channels != input_stream_params->channels) {
+ LOG("input_stream_params->layout does not match "
+ "input_stream_params->channels");
+ ret = CUBEB_ERROR_INVALID_PARAMETER;
+ goto error;
+ }
+ if (s->record.fd == -1) {
+ if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
+ LOG("Audio device \"%s\" could not be opened as read-only",
+ s->record.name);
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ }
+ if ((ret = oss_copy_params(s->record.fd, s, input_stream_params,
+ &s->record.info)) != CUBEB_OK) {
+ LOG("Setting record params failed");
+ goto error;
+ }
+ s->record.floating =
+ (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ s->record.frame_size =
+ s->record.info.channels * (s->record.info.precision / 8);
+ recnfr = (1 << oss_calc_frag_shift(s->nfr, s->record.frame_size)) /
+ s->record.frame_size;
+ }
+ if (output_stream_params != NULL) {
+ unsigned int nb_channels;
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ nb_channels =
+ cubeb_channel_layout_nb_channels(output_stream_params->layout);
+ if (output_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ nb_channels != output_stream_params->channels) {
+ LOG("output_stream_params->layout does not match "
+ "output_stream_params->channels");
+ ret = CUBEB_ERROR_INVALID_PARAMETER;
+ goto error;
+ }
+ if (s->play.fd == -1) {
+ if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
+ LOG("Audio device \"%s\" could not be opened as write-only",
+ s->play.name);
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ }
+ if ((ret = oss_copy_params(s->play.fd, s, output_stream_params,
+ &s->play.info)) != CUBEB_OK) {
+ LOG("Setting play params failed");
+ goto error;
+ }
+ s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8);
+ playnfr = (1 << oss_calc_frag_shift(s->nfr, s->play.frame_size)) /
+ s->play.frame_size;
+ }
+ /*
+ * Use the largest nframes among playing and recording streams to set OSS
+ * buffer size. After that, use the smallest allocated nframes among both
+ * direction to allocate our temporary buffers.
+ */
+ s->nfr = (playnfr > recnfr) ? playnfr : recnfr;
+ s->nfrags = OSS_NFRAGS;
+ if (s->play.fd != -1) {
+ int frag =
+ oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->play.frame_size));
+ if (ioctl(s->play.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
+ LOG("Failed to set play fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
+ frag);
+ audio_buf_info bi;
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi))
+ LOG("Failed to get play fd's buffer info.");
+ else {
+ if (bi.fragsize / s->play.frame_size < s->nfr)
+ s->nfr = bi.fragsize / s->play.frame_size;
+ }
+ }
+ if (s->record.fd != -1) {
+ int frag =
+ oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->record.frame_size));
+ if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
+ LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
+ frag);
+ audio_buf_info bi;
+ if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi))
+ LOG("Failed to get record fd's buffer info.");
+ else {
+ if (bi.fragsize / s->record.frame_size < s->nfr)
+ s->nfr = bi.fragsize / s->record.frame_size;
+ }
+ }
+ s->bufframes = s->nfr * s->nfrags;
+ s->context = context;
+ s->volume = 1.0;
+ s->state_cb = state_callback;
+ s->data_cb = data_callback;
+ s->user_ptr = user_ptr;
+
+ if (pthread_mutex_init(&s->mtx, NULL) != 0) {
+ LOG("Failed to create mutex");
+ goto error;
+ }
+ if (pthread_cond_init(&s->doorbell_cv, NULL) != 0) {
+ LOG("Failed to create cv");
+ goto error;
+ }
+ if (pthread_cond_init(&s->stopped_cv, NULL) != 0) {
+ LOG("Failed to create cv");
+ goto error;
+ }
+ s->doorbell = false;
+
+ if (s->play.fd != -1) {
+ if ((s->play.buf = calloc(s->bufframes, s->play.frame_size)) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ }
+ if (s->record.fd != -1) {
+ if ((s->record.buf = calloc(s->bufframes, s->record.frame_size)) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ }
+
+ *stream = s;
+ return CUBEB_OK;
+error:
+ if (s != NULL) {
+ oss_stream_destroy(s);
+ }
+ return ret;
+}
+
+static int
+oss_stream_thr_create(cubeb_stream * s)
+{
+ if (s->thread_created) {
+ s->doorbell = true;
+ pthread_cond_signal(&s->doorbell_cv);
+ return CUBEB_OK;
+ }
+
+ if (pthread_create(&s->thread, NULL, oss_io_routine, s) != 0) {
+ LOG("Couldn't create thread");
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_start(cubeb_stream * s)
+{
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
+ pthread_mutex_lock(&s->mtx);
+ /* Disallow starting an already started stream */
+ assert(!s->running && s->state != CUBEB_STATE_STARTED);
+ if (oss_stream_thr_create(s) != CUBEB_OK) {
+ pthread_mutex_unlock(&s->mtx);
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_ERROR);
+ return CUBEB_ERROR;
+ }
+ s->state = CUBEB_STATE_STARTED;
+ s->thread_created = true;
+ s->running = true;
+ pthread_mutex_unlock(&s->mtx);
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_get_position(cubeb_stream * s, uint64_t * position)
+{
+ pthread_mutex_lock(&s->mtx);
+ *position = s->frames_written;
+ pthread_mutex_unlock(&s->mtx);
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_get_latency(cubeb_stream * s, uint32_t * latency)
+{
+ int delay;
+
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETODELAY, &delay) == -1) {
+ return CUBEB_ERROR;
+ }
+
+ /* Return number of frames there */
+ *latency = delay / s->play.frame_size;
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_set_volume(cubeb_stream * stream, float volume)
+{
+ if (volume < 0.0)
+ volume = 0.0;
+ else if (volume > 1.0)
+ volume = 1.0;
+ pthread_mutex_lock(&stream->mtx);
+ stream->volume = volume;
+ pthread_mutex_unlock(&stream->mtx);
+ return CUBEB_OK;
+}
+
+static int
+oss_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
+{
+ *device = calloc(1, sizeof(cubeb_device));
+ if (*device == NULL) {
+ return CUBEB_ERROR;
+ }
+ (*device)->input_name =
+ stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
+ (*device)->output_name =
+ stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
+{
+ (void)stream;
+ free(device->input_name);
+ free(device->output_name);
+ free(device);
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const oss_ops = {
+ .init = oss_init,
+ .get_backend_id = oss_get_backend_id,
+ .get_max_channel_count = oss_get_max_channel_count,
+ .get_min_latency = oss_get_min_latency,
+ .get_preferred_sample_rate = oss_get_preferred_sample_rate,
+ .enumerate_devices = oss_enumerate_devices,
+ .device_collection_destroy = oss_device_collection_destroy,
+ .destroy = oss_destroy,
+ .stream_init = oss_stream_init,
+ .stream_destroy = oss_stream_destroy,
+ .stream_start = oss_stream_start,
+ .stream_stop = oss_stream_stop,
+ .stream_get_position = oss_stream_get_position,
+ .stream_get_latency = oss_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = oss_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = oss_get_current_device,
+ .stream_device_destroy = oss_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/media/libcubeb/src/cubeb_osx_run_loop.h b/media/libcubeb/src/cubeb_osx_run_loop.h
new file mode 100644
index 0000000000..8d88a37140
--- /dev/null
+++ b/media/libcubeb/src/cubeb_osx_run_loop.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2014 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* On OSX 10.6 and after, the notification callbacks from the audio hardware are
+ * called on the main thread. Setting the kAudioHardwarePropertyRunLoop property
+ * to null tells the OSX to use a separate thread for that.
+ *
+ * This has to be called only once per process, so it is in a separate header
+ * for easy integration in other code bases. */
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void
+cubeb_set_coreaudio_notification_runloop();
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c
deleted file mode 100644
index 4f474452d4..0000000000
--- a/media/libcubeb/src/cubeb_pulse.c
+++ /dev/null
@@ -1,1385 +0,0 @@
-/*
- * Copyright © 2011 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-#undef NDEBUG
-#include <assert.h>
-#include <dlfcn.h>
-#include <stdlib.h>
-#include <pulse/pulseaudio.h>
-#include <string.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-#include <stdio.h>
-
-#ifdef DISABLE_LIBPULSE_DLOPEN
-#define WRAP(x) x
-#else
-#define WRAP(x) cubeb_##x
-#define LIBPULSE_API_VISIT(X) \
- X(pa_channel_map_can_balance) \
- X(pa_channel_map_init_auto) \
- X(pa_context_connect) \
- X(pa_context_disconnect) \
- X(pa_context_drain) \
- X(pa_context_get_server_info) \
- X(pa_context_get_sink_info_by_name) \
- X(pa_context_get_sink_info_list) \
- X(pa_context_get_source_info_list) \
- X(pa_context_get_state) \
- X(pa_context_new) \
- X(pa_context_rttime_new) \
- X(pa_context_set_sink_input_volume) \
- X(pa_context_set_state_callback) \
- X(pa_context_unref) \
- X(pa_cvolume_set) \
- X(pa_cvolume_set_balance) \
- X(pa_frame_size) \
- X(pa_operation_get_state) \
- X(pa_operation_unref) \
- X(pa_proplist_gets) \
- X(pa_rtclock_now) \
- X(pa_stream_begin_write) \
- X(pa_stream_cancel_write) \
- X(pa_stream_connect_playback) \
- X(pa_stream_cork) \
- X(pa_stream_disconnect) \
- X(pa_stream_get_channel_map) \
- X(pa_stream_get_index) \
- X(pa_stream_get_latency) \
- X(pa_stream_get_sample_spec) \
- X(pa_stream_get_state) \
- X(pa_stream_get_time) \
- X(pa_stream_new) \
- X(pa_stream_set_state_callback) \
- X(pa_stream_set_write_callback) \
- X(pa_stream_unref) \
- X(pa_stream_update_timing_info) \
- X(pa_stream_write) \
- X(pa_sw_volume_from_linear) \
- X(pa_threaded_mainloop_free) \
- X(pa_threaded_mainloop_get_api) \
- X(pa_threaded_mainloop_in_thread) \
- X(pa_threaded_mainloop_lock) \
- X(pa_threaded_mainloop_new) \
- X(pa_threaded_mainloop_signal) \
- X(pa_threaded_mainloop_start) \
- X(pa_threaded_mainloop_stop) \
- X(pa_threaded_mainloop_unlock) \
- X(pa_threaded_mainloop_wait) \
- X(pa_usec_to_bytes) \
- X(pa_stream_set_read_callback) \
- X(pa_stream_connect_record) \
- X(pa_stream_readable_size) \
- X(pa_stream_writable_size) \
- X(pa_stream_peek) \
- X(pa_stream_drop) \
- X(pa_stream_get_buffer_attr) \
- X(pa_stream_get_device_name) \
- X(pa_context_set_subscribe_callback) \
- X(pa_context_subscribe) \
- X(pa_mainloop_api_once) \
-
-#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
-LIBPULSE_API_VISIT(MAKE_TYPEDEF);
-#undef MAKE_TYPEDEF
-#endif
-
-static struct cubeb_ops const pulse_ops;
-
-struct cubeb {
- struct cubeb_ops const * ops;
- void * libpulse;
- pa_threaded_mainloop * mainloop;
- pa_context * context;
- pa_sink_info * default_sink_info;
- char * context_name;
- int error;
- cubeb_device_collection_changed_callback collection_changed_callback;
- void * collection_changed_user_ptr;
-};
-
-struct cubeb_stream {
- cubeb * context;
- pa_stream * output_stream;
- pa_stream * input_stream;
- cubeb_data_callback data_callback;
- cubeb_state_callback state_callback;
- void * user_ptr;
- pa_time_event * drain_timer;
- pa_sample_spec output_sample_spec;
- pa_sample_spec input_sample_spec;
- int shutdown;
- float volume;
- cubeb_state state;
-};
-
-static const float PULSE_NO_GAIN = -1.0;
-
-enum cork_state {
- UNCORK = 0,
- CORK = 1 << 0,
- NOTIFY = 1 << 1
-};
-
-static void
-sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u)
-{
- (void)context;
- cubeb * ctx = u;
- if (!eol) {
- free(ctx->default_sink_info);
- ctx->default_sink_info = malloc(sizeof(pa_sink_info));
- memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info));
- }
- WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
-}
-
-static void
-server_info_callback(pa_context * context, const pa_server_info * info, void * u)
-{
- WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u);
-}
-
-static void
-context_state_callback(pa_context * c, void * u)
-{
- cubeb * ctx = u;
- if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(c))) {
- ctx->error = 1;
- }
- WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
-}
-
-static void
-context_notify_callback(pa_context * c, void * u)
-{
- (void)c;
- cubeb * ctx = u;
- WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
-}
-
-static void
-stream_success_callback(pa_stream * s, int success, void * u)
-{
- (void)s;
- (void)success;
- cubeb_stream * stm = u;
- WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
-}
-
-static void
-stream_state_change_callback(cubeb_stream * stm, cubeb_state s)
-{
- stm->state = s;
- stm->state_callback(stm, stm->user_ptr, s);
-}
-
-static void
-stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u)
-{
- (void)a;
- (void)tv;
- cubeb_stream * stm = u;
- assert(stm->drain_timer == e);
- stream_state_change_callback(stm, CUBEB_STATE_DRAINED);
- /* there's no pa_rttime_free, so use this instead. */
- a->time_free(stm->drain_timer);
- stm->drain_timer = NULL;
- WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
-}
-
-static void
-stream_state_callback(pa_stream * s, void * u)
-{
- cubeb_stream * stm = u;
- if (!PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(s))) {
- stream_state_change_callback(stm, CUBEB_STATE_ERROR);
- }
- WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
-}
-
-static void
-trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cubeb_stream * stm)
-{
- void * buffer;
- size_t size;
- int r;
- long got;
- size_t towrite, read_offset;
- size_t frame_size;
-
- frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
- assert(nbytes % frame_size == 0);
-
- towrite = nbytes;
- read_offset = 0;
- while (towrite) {
- size = towrite;
- r = WRAP(pa_stream_begin_write)(s, &buffer, &size);
- // Note: this has failed running under rr on occassion - needs investigation.
- assert(r == 0);
- assert(size > 0);
- assert(size % frame_size == 0);
-
- LOGV("Trigger user callback with output buffer size=%zd, read_offset=%zd", size, read_offset);
- got = stm->data_callback(stm, stm->user_ptr, (uint8_t const *)input_data + read_offset, buffer, size / frame_size);
- if (got < 0) {
- WRAP(pa_stream_cancel_write)(s);
- stm->shutdown = 1;
- return;
- }
- // If more iterations move offset of read buffer
- if (input_data) {
- size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
- read_offset += (size / frame_size) * in_frame_size;
- }
-
- if (stm->volume != PULSE_NO_GAIN) {
- uint32_t samples = size * stm->output_sample_spec.channels / frame_size ;
-
- if (stm->output_sample_spec.format == PA_SAMPLE_S16BE ||
- stm->output_sample_spec.format == PA_SAMPLE_S16LE) {
- short * b = buffer;
- for (uint32_t i = 0; i < samples; i++) {
- b[i] *= stm->volume;
- }
- } else {
- float * b = buffer;
- for (uint32_t i = 0; i < samples; i++) {
- b[i] *= stm->volume;
- }
- }
- }
-
- r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0, PA_SEEK_RELATIVE);
- assert(r == 0);
-
- if ((size_t) got < size / frame_size) {
- pa_usec_t latency = 0;
- r = WRAP(pa_stream_get_latency)(s, &latency, NULL);
- if (r == -PA_ERR_NODATA) {
- /* this needs a better guess. */
- latency = 100 * PA_USEC_PER_MSEC;
- }
- assert(r == 0 || r == -PA_ERR_NODATA);
- /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
- /* arbitrary safety margin: double the current latency. */
- assert(!stm->drain_timer);
- stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm);
- stm->shutdown = 1;
- return;
- }
-
- towrite -= size;
- }
-
- assert(towrite == 0);
-}
-
-static int
-read_from_input(pa_stream * s, void const ** buffer, size_t * size)
-{
- size_t readable_size = WRAP(pa_stream_readable_size)(s);
- if (readable_size > 0) {
- if (WRAP(pa_stream_peek)(s, buffer, size) < 0) {
- return -1;
- }
- }
- return readable_size;
-}
-
-static void
-stream_write_callback(pa_stream * s, size_t nbytes, void * u)
-{
- LOGV("Output callback to be written buffer size %zd", nbytes);
- cubeb_stream * stm = u;
- if (stm->shutdown ||
- stm->state != CUBEB_STATE_STARTED) {
- return;
- }
-
- if (!stm->input_stream){
- // Output/playback only operation.
- // Write directly to output
- assert(!stm->input_stream && stm->output_stream);
- trigger_user_callback(s, NULL, nbytes, stm);
- }
-}
-
-static void
-stream_read_callback(pa_stream * s, size_t nbytes, void * u)
-{
- LOGV("Input callback buffer size %zd", nbytes);
- cubeb_stream * stm = u;
- if (stm->shutdown) {
- return;
- }
-
- void const * read_data = NULL;
- size_t read_size;
- while (read_from_input(s, &read_data, &read_size) > 0) {
- /* read_data can be NULL in case of a hole. */
- if (read_data) {
- size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
- size_t read_frames = read_size / in_frame_size;
-
- if (stm->output_stream) {
- // input/capture + output/playback operation
- size_t out_frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
- size_t write_size = read_frames * out_frame_size;
- // Offer full duplex data for writing
- trigger_user_callback(stm->output_stream, read_data, write_size, stm);
- } else {
- // input/capture only operation. Call callback directly
- long got = stm->data_callback(stm, stm->user_ptr, read_data, NULL, read_frames);
- if (got < 0 || (size_t) got != read_frames) {
- WRAP(pa_stream_cancel_write)(s);
- stm->shutdown = 1;
- break;
- }
- }
- }
- if (read_size > 0) {
- WRAP(pa_stream_drop)(s);
- }
-
- if (stm->shutdown) {
- return;
- }
- }
-}
-
-static int
-wait_until_context_ready(cubeb * ctx)
-{
- for (;;) {
- pa_context_state_t state = WRAP(pa_context_get_state)(ctx->context);
- if (!PA_CONTEXT_IS_GOOD(state))
- return -1;
- if (state == PA_CONTEXT_READY)
- break;
- WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
- }
- return 0;
-}
-
-static int
-wait_until_io_stream_ready(pa_stream * stream, pa_threaded_mainloop * mainloop)
-{
- if (!stream || !mainloop){
- return -1;
- }
- for (;;) {
- pa_stream_state_t state = WRAP(pa_stream_get_state)(stream);
- if (!PA_STREAM_IS_GOOD(state))
- return -1;
- if (state == PA_STREAM_READY)
- break;
- WRAP(pa_threaded_mainloop_wait)(mainloop);
- }
- return 0;
-}
-
-static int
-wait_until_stream_ready(cubeb_stream * stm)
-{
- if (stm->output_stream &&
- wait_until_io_stream_ready(stm->output_stream, stm->context->mainloop) == -1) {
- return -1;
- }
- if(stm->input_stream &&
- wait_until_io_stream_ready(stm->input_stream, stm->context->mainloop) == -1) {
- return -1;
- }
- return 0;
-}
-
-static int
-operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o)
-{
- while (WRAP(pa_operation_get_state)(o) == PA_OPERATION_RUNNING) {
- WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
- if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context))) {
- return -1;
- }
- if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream))) {
- return -1;
- }
- }
- return 0;
-}
-
-static void
-cork_io_stream(cubeb_stream * stm, pa_stream * io_stream, enum cork_state state)
-{
- pa_operation * o;
- if (!io_stream) {
- return;
- }
- o = WRAP(pa_stream_cork)(io_stream, state & CORK, stream_success_callback, stm);
- if (o) {
- operation_wait(stm->context, io_stream, o);
- WRAP(pa_operation_unref)(o);
- }
-}
-
-static void
-stream_cork(cubeb_stream * stm, enum cork_state state)
-{
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- cork_io_stream(stm, stm->output_stream, state);
- cork_io_stream(stm, stm->input_stream, state);
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
-
- if (state & NOTIFY) {
- stream_state_change_callback(stm, state & CORK ? CUBEB_STATE_STOPPED
- : CUBEB_STATE_STARTED);
- }
-}
-
-static int
-stream_update_timing_info(cubeb_stream * stm)
-{
- int r = -1;
- pa_operation * o = NULL;
- if (stm->output_stream) {
- o = WRAP(pa_stream_update_timing_info)(stm->output_stream, stream_success_callback, stm);
- if (o) {
- r = operation_wait(stm->context, stm->output_stream, o);
- WRAP(pa_operation_unref)(o);
- }
- if (r != 0) {
- return r;
- }
- }
-
- if (stm->input_stream) {
- o = WRAP(pa_stream_update_timing_info)(stm->input_stream, stream_success_callback, stm);
- if (o) {
- r = operation_wait(stm->context, stm->input_stream, o);
- WRAP(pa_operation_unref)(o);
- }
- }
-
- return r;
-}
-
-static void pulse_context_destroy(cubeb * ctx);
-static void pulse_destroy(cubeb * ctx);
-
-static int
-pulse_context_init(cubeb * ctx)
-{
- if (ctx->context) {
- assert(ctx->error == 1);
- pulse_context_destroy(ctx);
- }
-
- ctx->context = WRAP(pa_context_new)(WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop),
- ctx->context_name);
- if (!ctx->context) {
- return -1;
- }
- WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx);
-
- WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
- WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);
-
- if (wait_until_context_ready(ctx) != 0) {
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
- pulse_context_destroy(ctx);
- ctx->context = NULL;
- return -1;
- }
-
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-
- ctx->error = 0;
-
- return 0;
-}
-
-/*static*/ int
-pulse_init(cubeb ** context, char const * context_name)
-{
- void * libpulse = NULL;
- cubeb * ctx;
-
- *context = NULL;
-
-#ifndef DISABLE_LIBPULSE_DLOPEN
- libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
- if (!libpulse) {
- return CUBEB_ERROR;
- }
-
-#define LOAD(x) { \
- cubeb_##x = dlsym(libpulse, #x); \
- if (!cubeb_##x) { \
- dlclose(libpulse); \
- return CUBEB_ERROR; \
- } \
- }
-
- LIBPULSE_API_VISIT(LOAD);
-#undef LOAD
-#endif
-
- ctx = calloc(1, sizeof(*ctx));
- assert(ctx);
-
- ctx->ops = &pulse_ops;
- ctx->libpulse = libpulse;
-
- ctx->mainloop = WRAP(pa_threaded_mainloop_new)();
- ctx->default_sink_info = NULL;
-
- WRAP(pa_threaded_mainloop_start)(ctx->mainloop);
-
- ctx->context_name = context_name ? strdup(context_name) : NULL;
- if (pulse_context_init(ctx) != 0) {
- pulse_destroy(ctx);
- return CUBEB_ERROR;
- }
-
- WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
- WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-
- *context = ctx;
-
- return CUBEB_OK;
-}
-
-static char const *
-pulse_get_backend_id(cubeb * ctx)
-{
- (void)ctx;
- return "pulse";
-}
-
-static int
-pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
-{
- (void)ctx;
- assert(ctx && max_channels);
-
- WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
- while (!ctx->default_sink_info) {
- WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
- }
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-
- *max_channels = ctx->default_sink_info->channel_map.channels;
-
- return CUBEB_OK;
-}
-
-static int
-pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
-{
- assert(ctx && rate);
- (void)ctx;
-
- WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
- while (!ctx->default_sink_info) {
- WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
- }
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-
- *rate = ctx->default_sink_info->sample_spec.rate;
-
- return CUBEB_OK;
-}
-
-static int
-pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
-{
- (void)ctx;
- // According to PulseAudio developers, this is a safe minimum.
- *latency_frames = 25 * params.rate / 1000;
-
- return CUBEB_OK;
-}
-
-static void
-pulse_context_destroy(cubeb * ctx)
-{
- pa_operation * o;
-
- WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
- o = WRAP(pa_context_drain)(ctx->context, context_notify_callback, ctx);
- if (o) {
- operation_wait(ctx, NULL, o);
- WRAP(pa_operation_unref)(o);
- }
- WRAP(pa_context_set_state_callback)(ctx->context, NULL, NULL);
- WRAP(pa_context_disconnect)(ctx->context);
- WRAP(pa_context_unref)(ctx->context);
- WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-}
-
-static void
-pulse_destroy(cubeb * ctx)
-{
- if (ctx->context_name) {
- free(ctx->context_name);
- }
- if (ctx->context) {
- pulse_context_destroy(ctx);
- }
-
- if (ctx->mainloop) {
- WRAP(pa_threaded_mainloop_stop)(ctx->mainloop);
- WRAP(pa_threaded_mainloop_free)(ctx->mainloop);
- }
-
- if (ctx->libpulse) {
- dlclose(ctx->libpulse);
- }
- if (ctx->default_sink_info) {
- free(ctx->default_sink_info);
- }
- free(ctx);
-}
-
-static void pulse_stream_destroy(cubeb_stream * stm);
-
-static pa_sample_format_t
-to_pulse_format(cubeb_sample_format format)
-{
- switch (format) {
- case CUBEB_SAMPLE_S16LE:
- return PA_SAMPLE_S16LE;
- case CUBEB_SAMPLE_S16BE:
- return PA_SAMPLE_S16BE;
- case CUBEB_SAMPLE_FLOAT32LE:
- return PA_SAMPLE_FLOAT32LE;
- case CUBEB_SAMPLE_FLOAT32BE:
- return PA_SAMPLE_FLOAT32BE;
- default:
- return PA_SAMPLE_INVALID;
- }
-}
-
-static int
-create_pa_stream(cubeb_stream * stm,
- pa_stream ** pa_stm,
- cubeb_stream_params * stream_params,
- char const * stream_name)
-{
- assert(stm && stream_params);
- *pa_stm = NULL;
- pa_sample_spec ss;
- ss.format = to_pulse_format(stream_params->format);
- if (ss.format == PA_SAMPLE_INVALID)
- return CUBEB_ERROR_INVALID_FORMAT;
- ss.rate = stream_params->rate;
- ss.channels = stream_params->channels;
-
- *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
- return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK;
-}
-
-static pa_buffer_attr
-set_buffering_attribute(unsigned int latency_frames, pa_sample_spec * sample_spec)
-{
- pa_buffer_attr battr;
- battr.maxlength = -1;
- battr.prebuf = -1;
- battr.tlength = latency_frames * WRAP(pa_frame_size)(sample_spec);
- battr.minreq = battr.tlength / 4;
- battr.fragsize = battr.minreq;
-
- LOG("Requested buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",
- battr.maxlength, battr.tlength, battr.prebuf, battr.minreq, battr.fragsize);
-
- return battr;
-}
-
-static int
-pulse_stream_init(cubeb * context,
- cubeb_stream ** stream,
- char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
-{
- cubeb_stream * stm;
- pa_buffer_attr battr;
- int r;
-
- assert(context);
-
- // If the connection failed for some reason, try to reconnect
- if (context->error == 1 && pulse_context_init(context) != 0) {
- return CUBEB_ERROR;
- }
-
- *stream = NULL;
-
- stm = calloc(1, sizeof(*stm));
- assert(stm);
-
- stm->context = context;
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
- stm->volume = PULSE_NO_GAIN;
- stm->state = -1;
- assert(stm->shutdown == 0);
-
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- if (output_stream_params) {
- r = create_pa_stream(stm, &stm->output_stream, output_stream_params, stream_name);
- if (r != CUBEB_OK) {
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
- pulse_stream_destroy(stm);
- return r;
- }
-
- stm->output_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->output_stream));
-
- WRAP(pa_stream_set_state_callback)(stm->output_stream, stream_state_callback, stm);
- WRAP(pa_stream_set_write_callback)(stm->output_stream, stream_write_callback, stm);
-
- battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec);
- WRAP(pa_stream_connect_playback)(stm->output_stream,
- output_device,
- &battr,
- PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
- PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY,
- NULL, NULL);
- }
-
- // Set up input stream
- if (input_stream_params) {
- r = create_pa_stream(stm, &stm->input_stream, input_stream_params, stream_name);
- if (r != CUBEB_OK) {
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
- pulse_stream_destroy(stm);
- return r;
- }
-
- stm->input_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->input_stream));
-
- WRAP(pa_stream_set_state_callback)(stm->input_stream, stream_state_callback, stm);
- WRAP(pa_stream_set_read_callback)(stm->input_stream, stream_read_callback, stm);
-
- battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec);
- WRAP(pa_stream_connect_record)(stm->input_stream,
- input_device,
- &battr,
- PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
- PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY);
- }
-
- r = wait_until_stream_ready(stm);
- if (r == 0) {
- /* force a timing update now, otherwise timing info does not become valid
- until some point after initialization has completed. */
- r = stream_update_timing_info(stm);
- }
-
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
-
- if (r != 0) {
- pulse_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- if (g_log_level) {
- if (output_stream_params){
- const pa_buffer_attr * output_att;
- output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream);
- LOG("Output buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",output_att->maxlength, output_att->tlength,
- output_att->prebuf, output_att->minreq, output_att->fragsize);
- }
-
- if (input_stream_params){
- const pa_buffer_attr * input_att;
- input_att = WRAP(pa_stream_get_buffer_attr)(stm->input_stream);
- LOG("Input buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",input_att->maxlength, input_att->tlength,
- input_att->prebuf, input_att->minreq, input_att->fragsize);
- }
- }
-
- *stream = stm;
-
- return CUBEB_OK;
-}
-
-static void
-pulse_stream_destroy(cubeb_stream * stm)
-{
- stream_cork(stm, CORK);
-
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- if (stm->output_stream) {
-
- if (stm->drain_timer) {
- /* there's no pa_rttime_free, so use this instead. */
- WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop)->time_free(stm->drain_timer);
- }
-
- WRAP(pa_stream_set_state_callback)(stm->output_stream, NULL, NULL);
- WRAP(pa_stream_set_write_callback)(stm->output_stream, NULL, NULL);
- WRAP(pa_stream_disconnect)(stm->output_stream);
- WRAP(pa_stream_unref)(stm->output_stream);
- }
-
- if (stm->input_stream) {
- WRAP(pa_stream_set_state_callback)(stm->input_stream, NULL, NULL);
- WRAP(pa_stream_set_read_callback)(stm->input_stream, NULL, NULL);
- WRAP(pa_stream_disconnect)(stm->input_stream);
- WRAP(pa_stream_unref)(stm->input_stream);
- }
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
-
- free(stm);
-}
-
-static void
-pulse_defer_event_cb(pa_mainloop_api * a, void * userdata)
-{
- (void)a;
- cubeb_stream * stm = userdata;
- if (stm->shutdown) {
- return;
- }
- size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream);
- trigger_user_callback(stm->output_stream, NULL, writable_size, stm);
-}
-
-static int
-pulse_stream_start(cubeb_stream * stm)
-{
- stm->shutdown = 0;
- stream_cork(stm, UNCORK | NOTIFY);
-
- if (stm->output_stream && !stm->input_stream) {
- /* On output only case need to manually call user cb once in order to make
- * things roll. This is done via a defer event in order to execute it
- * from PA server thread. */
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- WRAP(pa_mainloop_api_once)(WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop),
- pulse_defer_event_cb, stm);
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
- }
-
- return CUBEB_OK;
-}
-
-static int
-pulse_stream_stop(cubeb_stream * stm)
-{
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- stm->shutdown = 1;
- // If draining is taking place wait to finish
- while (stm->drain_timer) {
- WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
- }
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
-
- stream_cork(stm, CORK | NOTIFY);
- return CUBEB_OK;
-}
-
-static int
-pulse_stream_get_position(cubeb_stream * stm, uint64_t * position)
-{
- int r, in_thread;
- pa_usec_t r_usec;
- uint64_t bytes;
-
- if (!stm || !stm->output_stream) {
- return CUBEB_ERROR;
- }
-
- in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop);
-
- if (!in_thread) {
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
- }
- r = WRAP(pa_stream_get_time)(stm->output_stream, &r_usec);
- if (!in_thread) {
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
- }
-
- if (r != 0) {
- return CUBEB_ERROR;
- }
-
- bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->output_sample_spec);
- *position = bytes / WRAP(pa_frame_size)(&stm->output_sample_spec);
-
- return CUBEB_OK;
-}
-
-static int
-pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
-{
- pa_usec_t r_usec;
- int negative, r;
-
- if (!stm || !stm->output_stream) {
- return CUBEB_ERROR;
- }
-
- r = WRAP(pa_stream_get_latency)(stm->output_stream, &r_usec, &negative);
- assert(!negative);
- if (r) {
- return CUBEB_ERROR;
- }
-
- *latency = r_usec * stm->output_sample_spec.rate / PA_USEC_PER_SEC;
- return CUBEB_OK;
-}
-
-static void
-volume_success(pa_context *c, int success, void *userdata)
-{
- (void)success;
- (void)c;
- cubeb_stream * stream = userdata;
- assert(success);
- WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
-}
-
-static int
-pulse_stream_set_volume(cubeb_stream * stm, float volume)
-{
- uint32_t index;
- pa_operation * op;
- pa_volume_t vol;
- pa_cvolume cvol;
- const pa_sample_spec * ss;
-
- if (!stm->output_stream) {
- return CUBEB_ERROR;
- }
-
- WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
-
- while (!stm->context->default_sink_info) {
- WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
- }
-
- /* if the pulse daemon is configured to use flat volumes,
- * apply our own gain instead of changing the input volume on the sink. */
- if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) {
- stm->volume = volume;
- } else {
- ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream);
-
- vol = WRAP(pa_sw_volume_from_linear)(volume);
- WRAP(pa_cvolume_set)(&cvol, ss->channels, vol);
-
- index = WRAP(pa_stream_get_index)(stm->output_stream);
-
- op = WRAP(pa_context_set_sink_input_volume)(stm->context->context,
- index, &cvol, volume_success,
- stm);
- if (op) {
- operation_wait(stm->context, stm->output_stream, op);
- WRAP(pa_operation_unref)(op);
- }
- }
-
- WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
-
- return CUBEB_OK;
-}
-
-static int
-pulse_stream_set_panning(cubeb_stream * stream, float panning)
-{
- const pa_channel_map * map;
- pa_cvolume vol;
-
- if (!stream->output_stream) {
- return CUBEB_ERROR;
- }
-
- map = WRAP(pa_stream_get_channel_map)(stream->output_stream);
-
- if (!WRAP(pa_channel_map_can_balance)(map)) {
- return CUBEB_ERROR;
- }
-
- WRAP(pa_cvolume_set_balance)(&vol, map, panning);
-
- return CUBEB_OK;
-}
-
-typedef struct {
- char * default_sink_name;
- char * default_source_name;
-
- cubeb_device_info ** devinfo;
- uint32_t max;
- uint32_t count;
- cubeb * context;
-} pulse_dev_list_data;
-
-static cubeb_device_fmt
-pulse_format_to_cubeb_format(pa_sample_format_t format)
-{
- switch (format) {
- case PA_SAMPLE_S16LE:
- return CUBEB_DEVICE_FMT_S16LE;
- case PA_SAMPLE_S16BE:
- return CUBEB_DEVICE_FMT_S16BE;
- case PA_SAMPLE_FLOAT32LE:
- return CUBEB_DEVICE_FMT_F32LE;
- case PA_SAMPLE_FLOAT32BE:
- return CUBEB_DEVICE_FMT_F32BE;
- default:
- return CUBEB_DEVICE_FMT_F32NE;
- }
-}
-
-static void
-pulse_ensure_dev_list_data_list_size (pulse_dev_list_data * list_data)
-{
- if (list_data->count == list_data->max) {
- list_data->max += 8;
- list_data->devinfo = realloc(list_data->devinfo,
- sizeof(cubeb_device_info *) * list_data->max);
- }
-}
-
-static cubeb_device_state
-pulse_get_state_from_sink_port(pa_sink_port_info * info)
-{
- if (info != NULL) {
-#if PA_CHECK_VERSION(2, 0, 0)
- if (info->available == PA_PORT_AVAILABLE_NO)
- return CUBEB_DEVICE_STATE_UNPLUGGED;
- else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
-#endif
- return CUBEB_DEVICE_STATE_ENABLED;
- }
-
- return CUBEB_DEVICE_STATE_DISABLED;
-}
-
-static void
-pulse_sink_info_cb(pa_context * context, const pa_sink_info * info,
- int eol, void * user_data)
-{
- pulse_dev_list_data * list_data = user_data;
- cubeb_device_info * devinfo;
- const char * prop;
-
- (void)context;
-
- if (eol || info == NULL)
- return;
-
- devinfo = calloc(1, sizeof(cubeb_device_info));
-
- devinfo->device_id = strdup(info->name);
- devinfo->devid = devinfo->device_id;
- devinfo->friendly_name = strdup(info->description);
- prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
- if (prop)
- devinfo->group_id = strdup(prop);
- prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
- if (prop)
- devinfo->vendor_name = strdup(prop);
-
- devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT;
- devinfo->state = pulse_get_state_from_sink_port(info->active_port);
- devinfo->preferred = strcmp(info->name, list_data->default_sink_name) == 0;
-
- devinfo->format = CUBEB_DEVICE_FMT_ALL;
- devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
- devinfo->max_channels = info->channel_map.channels;
- devinfo->min_rate = 1;
- devinfo->max_rate = PA_RATE_MAX;
- devinfo->default_rate = info->sample_spec.rate;
-
- devinfo->latency_lo = 0;
- devinfo->latency_hi = 0;
-
- pulse_ensure_dev_list_data_list_size (list_data);
- list_data->devinfo[list_data->count++] = devinfo;
-
- WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
-}
-
-static cubeb_device_state
-pulse_get_state_from_source_port(pa_source_port_info * info)
-{
- if (info != NULL) {
-#if PA_CHECK_VERSION(2, 0, 0)
- if (info->available == PA_PORT_AVAILABLE_NO)
- return CUBEB_DEVICE_STATE_UNPLUGGED;
- else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
-#endif
- return CUBEB_DEVICE_STATE_ENABLED;
- }
-
- return CUBEB_DEVICE_STATE_DISABLED;
-}
-
-static void
-pulse_source_info_cb(pa_context * context, const pa_source_info * info,
- int eol, void * user_data)
-{
- pulse_dev_list_data * list_data = user_data;
- cubeb_device_info * devinfo;
- const char * prop;
-
- (void)context;
-
- if (eol)
- return;
-
- devinfo = calloc(1, sizeof(cubeb_device_info));
-
- devinfo->device_id = strdup(info->name);
- devinfo->devid = devinfo->device_id;
- devinfo->friendly_name = strdup(info->description);
- prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
- if (prop)
- devinfo->group_id = strdup(prop);
- prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
- if (prop)
- devinfo->vendor_name = strdup(prop);
-
- devinfo->type = CUBEB_DEVICE_TYPE_INPUT;
- devinfo->state = pulse_get_state_from_source_port(info->active_port);
- devinfo->preferred = strcmp(info->name, list_data->default_source_name) == 0;
-
- devinfo->format = CUBEB_DEVICE_FMT_ALL;
- devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
- devinfo->max_channels = info->channel_map.channels;
- devinfo->min_rate = 1;
- devinfo->max_rate = PA_RATE_MAX;
- devinfo->default_rate = info->sample_spec.rate;
-
- devinfo->latency_lo = 0;
- devinfo->latency_hi = 0;
-
- pulse_ensure_dev_list_data_list_size (list_data);
- list_data->devinfo[list_data->count++] = devinfo;
-
- WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
-}
-
-static void
-pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata)
-{
- pulse_dev_list_data * list_data = userdata;
-
- (void)c;
-
- free(list_data->default_sink_name);
- free(list_data->default_source_name);
- list_data->default_sink_name = strdup(i->default_sink_name);
- list_data->default_source_name = strdup(i->default_source_name);
-
- WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
-}
-
-static int
-pulse_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection)
-{
- pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0, context };
- pa_operation * o;
- uint32_t i;
-
- WRAP(pa_threaded_mainloop_lock)(context->mainloop);
-
- o = WRAP(pa_context_get_server_info)(context->context,
- pulse_server_info_cb, &user_data);
- if (o) {
- operation_wait(context, NULL, o);
- WRAP(pa_operation_unref)(o);
- }
-
- if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
- o = WRAP(pa_context_get_sink_info_list)(context->context,
- pulse_sink_info_cb, &user_data);
- if (o) {
- operation_wait(context, NULL, o);
- WRAP(pa_operation_unref)(o);
- }
- }
-
- if (type & CUBEB_DEVICE_TYPE_INPUT) {
- o = WRAP(pa_context_get_source_info_list)(context->context,
- pulse_source_info_cb, &user_data);
- if (o) {
- operation_wait(context, NULL, o);
- WRAP(pa_operation_unref)(o);
- }
- }
-
- WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
-
- *collection = malloc(sizeof(cubeb_device_collection) +
- sizeof(cubeb_device_info *) * (user_data.count > 0 ? user_data.count - 1 : 0));
- (*collection)->count = user_data.count;
- for (i = 0; i < user_data.count; i++)
- (*collection)->device[i] = user_data.devinfo[i];
-
- free(user_data.default_sink_name);
- free(user_data.default_source_name);
- free(user_data.devinfo);
- return CUBEB_OK;
-}
-
-static int
-pulse_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device)
-{
-#if PA_CHECK_VERSION(0, 9, 8)
- *device = calloc(1, sizeof(cubeb_device));
- if (*device == NULL)
- return CUBEB_ERROR;
-
- if (stm->input_stream) {
- const char * name = WRAP(pa_stream_get_device_name)(stm->input_stream);
- (*device)->input_name = (name == NULL) ? NULL : strdup(name);
- }
-
- if (stm->output_stream) {
- const char * name = WRAP(pa_stream_get_device_name)(stm->output_stream);
- (*device)->output_name = (name == NULL) ? NULL : strdup(name);
- }
-
- return CUBEB_OK;
-#else
- return CUBEB_ERROR_NOT_SUPPORTED;
-#endif
-}
-
-static int
-pulse_stream_device_destroy(cubeb_stream * stream,
- cubeb_device * device)
-{
- (void)stream;
- free(device->input_name);
- free(device->output_name);
- free(device);
- return CUBEB_OK;
-}
-
-static void
-pulse_subscribe_callback(pa_context * ctx,
- pa_subscription_event_type_t t,
- uint32_t index, void * userdata)
-{
- (void)ctx;
- cubeb * context = userdata;
-
- switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
- case PA_SUBSCRIPTION_EVENT_SOURCE:
- case PA_SUBSCRIPTION_EVENT_SINK:
-
- if (g_log_level) {
- if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
- (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
- LOG("Removing sink index %d", index);
- } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
- (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
- LOG("Adding sink index %d", index);
- }
- if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
- (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
- LOG("Removing source index %d", index);
- } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
- (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
- LOG("Adding source index %d", index);
- }
- }
-
- if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE ||
- (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
- context->collection_changed_callback(context, context->collection_changed_user_ptr);
- }
- break;
- }
-}
-
-static void
-subscribe_success(pa_context *c, int success, void *userdata)
-{
- (void)c;
- cubeb * context = userdata;
- assert(success);
- WRAP(pa_threaded_mainloop_signal)(context->mainloop, 0);
-}
-
-static int
-pulse_register_device_collection_changed(cubeb * context,
- cubeb_device_type devtype,
- cubeb_device_collection_changed_callback collection_changed_callback,
- void * user_ptr)
-{
- context->collection_changed_callback = collection_changed_callback;
- context->collection_changed_user_ptr = user_ptr;
-
- WRAP(pa_threaded_mainloop_lock)(context->mainloop);
-
- pa_subscription_mask_t mask;
- if (context->collection_changed_callback == NULL) {
- // Unregister subscription
- WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL);
- mask = PA_SUBSCRIPTION_MASK_NULL;
- } else {
- WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context);
- if (devtype == CUBEB_DEVICE_TYPE_INPUT)
- mask = PA_SUBSCRIPTION_MASK_SOURCE;
- else if (devtype == CUBEB_DEVICE_TYPE_OUTPUT)
- mask = PA_SUBSCRIPTION_MASK_SINK;
- else
- mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE;
- }
-
- pa_operation * o;
- o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context);
- if (o == NULL) {
- LOG("Context subscribe failed");
- return CUBEB_ERROR;
- }
- operation_wait(context, NULL, o);
- WRAP(pa_operation_unref)(o);
-
- WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
-
- return CUBEB_OK;
-}
-
-static struct cubeb_ops const pulse_ops = {
- .init = pulse_init,
- .get_backend_id = pulse_get_backend_id,
- .get_max_channel_count = pulse_get_max_channel_count,
- .get_min_latency = pulse_get_min_latency,
- .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
- .enumerate_devices = pulse_enumerate_devices,
- .destroy = pulse_destroy,
- .stream_init = pulse_stream_init,
- .stream_destroy = pulse_stream_destroy,
- .stream_start = pulse_stream_start,
- .stream_stop = pulse_stream_stop,
- .stream_get_position = pulse_stream_get_position,
- .stream_get_latency = pulse_stream_get_latency,
- .stream_set_volume = pulse_stream_set_volume,
- .stream_set_panning = pulse_stream_set_panning,
- .stream_get_current_device = pulse_stream_get_current_device,
- .stream_device_destroy = pulse_stream_device_destroy,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = pulse_register_device_collection_changed
-};
diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp
index f6676946c0..d61b1daf26 100644
--- a/media/libcubeb/src/cubeb_resampler.cpp
+++ b/media/libcubeb/src/cubeb_resampler.cpp
@@ -8,21 +8,21 @@
#define NOMINMAX
#endif // NOMINMAX
-#include <algorithm>
-#include <cmath>
-#include <cassert>
-#include <cstring>
-#include <cstddef>
-#include <cstdio>
#include "cubeb_resampler.h"
#include "cubeb-speex-resampler.h"
#include "cubeb_resampler_internal.h"
#include "cubeb_utils.h"
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstddef>
+#include <cstdio>
+#include <cstring>
int
to_speex_quality(cubeb_resampler_quality q)
{
- switch(q) {
+ switch (q) {
case CUBEB_RESAMPLER_QUALITY_VOIP:
return SPEEX_RESAMPLER_QUALITY_VOIP;
case CUBEB_RESAMPLER_QUALITY_DEFAULT:
@@ -35,157 +35,230 @@ to_speex_quality(cubeb_resampler_quality q)
}
}
-long noop_resampler::fill(void * input_buffer, long * input_frames_count,
- void * output_buffer, long output_frames)
+uint32_t
+min_buffered_audio_frame(uint32_t sample_rate)
+{
+ return sample_rate / 20;
+}
+
+template <typename T>
+passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
+ cubeb_data_callback cb,
+ void * ptr,
+ uint32_t input_channels,
+ uint32_t sample_rate)
+ : processor(input_channels), stream(s), data_callback(cb), user_ptr(ptr),
+ sample_rate(sample_rate)
+{
+}
+
+template <typename T>
+long
+passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long output_frames)
{
if (input_buffer) {
assert(input_frames_count);
}
- assert((input_buffer && output_buffer &&
- *input_frames_count >= output_frames) ||
- (!input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
- (!output_buffer && output_frames == 0));
-
- if (output_buffer == nullptr) {
- assert(input_buffer);
+ assert((input_buffer && output_buffer) ||
+ (output_buffer && !input_buffer &&
+ (!input_frames_count || *input_frames_count == 0)) ||
+ (input_buffer && !output_buffer && output_frames == 0));
+
+ // When we have no pending input data and exactly as much input
+ // as output data, we don't need to copy it into the internal buffer
+ // and can directly forward it to the callback.
+ void * in_buf = input_buffer;
+ unsigned long pop_input_count = 0u;
+ if (input_buffer && !output_buffer) {
output_frames = *input_frames_count;
+ } else if (input_buffer) {
+ if (internal_input_buffer.length() != 0 ||
+ *input_frames_count < output_frames) {
+ // If we have pending input data left and have to first append the input
+ // so we can pass it as one pointer to the callback. Or this is a glitch.
+ // It can happen when system's performance is poor. Audible silence is
+ // being pushed at the end of the short input buffer. An improvement for
+ // the future is to resample to the output number of frames, when that
+ // happens.
+ internal_input_buffer.push(static_cast<T *>(input_buffer),
+ frames_to_samples(*input_frames_count));
+ if (internal_input_buffer.length() < frames_to_samples(output_frames)) {
+ // This is unxpected but it can happen when a glitch occurs. Fill the
+ // buffer with silence. First keep the actual number of input samples
+ // used without the silence.
+ pop_input_count = internal_input_buffer.length();
+ internal_input_buffer.push_silence(frames_to_samples(output_frames) -
+ internal_input_buffer.length());
+ } else {
+ pop_input_count = frames_to_samples(output_frames);
+ }
+ in_buf = internal_input_buffer.data();
+ } else if (*input_frames_count > output_frames) {
+ // In this case we have more input that we need output and
+ // fill the overflowing input into internal_input_buffer
+ // Since we have no other pending data, we can nonetheless
+ // pass the current input data directly to the callback
+ assert(pop_input_count == 0);
+ unsigned long samples_off = frames_to_samples(output_frames);
+ internal_input_buffer.push(
+ static_cast<T *>(input_buffer) + samples_off,
+ frames_to_samples(*input_frames_count - output_frames));
+ }
}
- if (input_buffer && *input_frames_count != output_frames) {
- assert(*input_frames_count > output_frames);
- *input_frames_count = output_frames;
+ long rv =
+ data_callback(stream, user_ptr, in_buf, output_buffer, output_frames);
+
+ if (input_buffer) {
+ if (pop_input_count) {
+ internal_input_buffer.pop(nullptr, pop_input_count);
+ *input_frames_count = samples_to_frames(pop_input_count);
+ } else {
+ *input_frames_count = output_frames;
+ }
+ drop_audio_if_needed();
}
- return data_callback(stream, user_ptr,
- input_buffer, output_buffer, output_frames);
+ return rv;
}
-template<typename T, typename InputProcessor, typename OutputProcessor>
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
- ::cubeb_resampler_speex(InputProcessor * input_processor,
- OutputProcessor * output_processor,
- cubeb_stream * s,
- cubeb_data_callback cb,
- void * ptr)
- : input_processor(input_processor)
- , output_processor(output_processor)
- , stream(s)
- , data_callback(cb)
- , user_ptr(ptr)
+// Explicit instantiation of template class.
+template class passthrough_resampler<float>;
+template class passthrough_resampler<short>;
+
+template <typename T, typename InputProcessor, typename OutputProcessor>
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::
+ cubeb_resampler_speex(InputProcessor * input_processor,
+ OutputProcessor * output_processor, cubeb_stream * s,
+ cubeb_data_callback cb, void * ptr)
+ : input_processor(input_processor), output_processor(output_processor),
+ stream(s), data_callback(cb), user_ptr(ptr)
{
if (input_processor && output_processor) {
- // Add some delay on the processor that has the lowest delay so that the
- // streams are synchronized.
- uint32_t in_latency = input_processor->latency();
- uint32_t out_latency = output_processor->latency();
- if (in_latency > out_latency) {
- uint32_t latency_diff = in_latency - out_latency;
- output_processor->add_latency(latency_diff);
- } else if (in_latency < out_latency) {
- uint32_t latency_diff = out_latency - in_latency;
- input_processor->add_latency(latency_diff);
- }
fill_internal = &cubeb_resampler_speex::fill_internal_duplex;
- } else if (input_processor) {
+ } else if (input_processor) {
fill_internal = &cubeb_resampler_speex::fill_internal_input;
- } else if (output_processor) {
+ } else if (output_processor) {
fill_internal = &cubeb_resampler_speex::fill_internal_output;
}
}
-template<typename T, typename InputProcessor, typename OutputProcessor>
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
- ::~cubeb_resampler_speex()
-{ }
+template <typename T, typename InputProcessor, typename OutputProcessor>
+cubeb_resampler_speex<T, InputProcessor,
+ OutputProcessor>::~cubeb_resampler_speex()
+{
+}
-template<typename T, typename InputProcessor, typename OutputProcessor>
+template <typename T, typename InputProcessor, typename OutputProcessor>
long
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
-::fill(void * input_buffer, long * input_frames_count,
- void * output_buffer, long output_frames_needed)
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill(
+ void * input_buffer, long * input_frames_count, void * output_buffer,
+ long output_frames_needed)
{
/* Input and output buffers, typed */
- T * in_buffer = reinterpret_cast<T*>(input_buffer);
- T * out_buffer = reinterpret_cast<T*>(output_buffer);
- return (this->*fill_internal)(in_buffer, input_frames_count,
- out_buffer, output_frames_needed);
+ T * in_buffer = reinterpret_cast<T *>(input_buffer);
+ T * out_buffer = reinterpret_cast<T *>(output_buffer);
+ return (this->*fill_internal)(in_buffer, input_frames_count, out_buffer,
+ output_frames_needed);
}
-template<typename T, typename InputProcessor, typename OutputProcessor>
+template <typename T, typename InputProcessor, typename OutputProcessor>
long
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
-::fill_internal_output(T * input_buffer, long * input_frames_count,
- T * output_buffer, long output_frames_needed)
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_output(
+ T * input_buffer, long * input_frames_count, T * output_buffer,
+ long output_frames_needed)
{
assert(!input_buffer && (!input_frames_count || *input_frames_count == 0) &&
output_buffer && output_frames_needed);
- long got = 0;
- T * out_unprocessed = nullptr;
- long output_frames_before_processing = 0;
+ if (!draining) {
+ long got = 0;
+ T * out_unprocessed = nullptr;
+ long output_frames_before_processing = 0;
+ /* fill directly the input buffer of the output processor to save a copy */
+ output_frames_before_processing =
+ output_processor->input_needed_for_output(output_frames_needed);
- /* fill directly the input buffer of the output processor to save a copy */
- output_frames_before_processing =
- output_processor->input_needed_for_output(output_frames_needed);
+ out_unprocessed =
+ output_processor->input_buffer(output_frames_before_processing);
- out_unprocessed =
- output_processor->input_buffer(output_frames_before_processing);
+ got = data_callback(stream, user_ptr, nullptr, out_unprocessed,
+ output_frames_before_processing);
- got = data_callback(stream, user_ptr,
- nullptr, out_unprocessed,
- output_frames_before_processing);
+ if (got < output_frames_before_processing) {
+ draining = true;
- if (got < 0) {
- return got;
- }
+ if (got < 0) {
+ return got;
+ }
+ }
- output_processor->written(got);
+ output_processor->written(got);
+ }
/* Process the output. If not enough frames have been returned from the
- * callback, drain the processors. */
+ * callback, drain the processors. */
return output_processor->output(output_buffer, output_frames_needed);
}
-template<typename T, typename InputProcessor, typename OutputProcessor>
+template <typename T, typename InputProcessor, typename OutputProcessor>
long
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
-::fill_internal_input(T * input_buffer, long * input_frames_count,
- T * output_buffer, long /*output_frames_needed*/)
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_input(
+ T * input_buffer, long * input_frames_count, T * output_buffer,
+ long /*output_frames_needed*/)
{
assert(input_buffer && input_frames_count && *input_frames_count &&
!output_buffer);
- /* The input data, after eventual resampling. This is passed to the callback. */
+ /* The input data, after eventual resampling. This is passed to the callback.
+ */
T * resampled_input = nullptr;
- uint32_t resampled_frame_count = input_processor->output_for_input(*input_frames_count);
+ uint32_t resampled_frame_count =
+ input_processor->output_for_input(*input_frames_count);
/* process the input, and present exactly `output_frames_needed` in the
- * callback. */
+ * callback. */
input_processor->input(input_buffer, *input_frames_count);
- resampled_input = input_processor->output(resampled_frame_count);
- long got = data_callback(stream, user_ptr,
- resampled_input, nullptr, resampled_frame_count);
+ /* resampled_frame_count == 0 happens if the resampler
+ * doesn't have enough input frames buffered to produce 1 resampled frame. */
+ if (resampled_frame_count == 0) {
+ return *input_frames_count;
+ }
+
+ size_t frames_resampled = 0;
+ resampled_input =
+ input_processor->output(resampled_frame_count, &frames_resampled);
+ *input_frames_count = frames_resampled;
+
+ long got = data_callback(stream, user_ptr, resampled_input, nullptr,
+ resampled_frame_count);
/* Return the number of initial input frames or part of it.
- * Since output_frames_needed == 0 in input scenario, the only
- * available number outside resampler is the initial number of frames. */
+ * Since output_frames_needed == 0 in input scenario, the only
+ * available number outside resampler is the initial number of frames. */
return (*input_frames_count) * (got / resampled_frame_count);
}
-
-template<typename T, typename InputProcessor, typename OutputProcessor>
+template <typename T, typename InputProcessor, typename OutputProcessor>
long
-cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
-::fill_internal_duplex(T * in_buffer, long * input_frames_count,
- T * out_buffer, long output_frames_needed)
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_duplex(
+ T * in_buffer, long * input_frames_count, T * out_buffer,
+ long output_frames_needed)
{
- /* The input data, after eventual resampling. This is passed to the callback. */
+ if (draining) {
+ // discard input and drain any signal remaining in the resampler.
+ return output_processor->output(out_buffer, output_frames_needed);
+ }
+
+ /* The input data, after eventual resampling. This is passed to the callback.
+ */
T * resampled_input = nullptr;
/* The output buffer passed down in the callback, that might be resampled. */
T * out_unprocessed = nullptr;
- size_t output_frames_before_processing = 0;
+ long output_frames_before_processing = 0;
/* The number of frames returned from the callback. */
long got = 0;
@@ -201,34 +274,46 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
* caller. */
output_frames_before_processing =
- output_processor->input_needed_for_output(output_frames_needed);
- /* fill directly the input buffer of the output processor to save a copy */
+ output_processor->input_needed_for_output(output_frames_needed);
+ /* fill directly the input buffer of the output processor to save a copy */
out_unprocessed =
- output_processor->input_buffer(output_frames_before_processing);
+ output_processor->input_buffer(output_frames_before_processing);
if (in_buffer) {
/* process the input, and present exactly `output_frames_needed` in the
- * callback. */
+ * callback. */
input_processor->input(in_buffer, *input_frames_count);
- resampled_input =
- input_processor->output(output_frames_before_processing);
+
+ size_t frames_resampled = 0;
+ resampled_input = input_processor->output(output_frames_before_processing,
+ &frames_resampled);
+ *input_frames_count = frames_resampled;
} else {
resampled_input = nullptr;
}
- got = data_callback(stream, user_ptr,
- resampled_input, out_unprocessed,
+ got = data_callback(stream, user_ptr, resampled_input, out_unprocessed,
output_frames_before_processing);
- if (got < 0) {
- return got;
+ if (got < output_frames_before_processing) {
+ draining = true;
+
+ if (got < 0) {
+ return got;
+ }
}
output_processor->written(got);
+ input_processor->drop_audio_if_needed();
+
/* Process the output. If not enough frames have been returned from the
* callback, drain the processors. */
- return output_processor->output(out_buffer, output_frames_needed);
+ got = output_processor->output(out_buffer, output_frames_needed);
+
+ output_processor->drop_audio_if_needed();
+
+ return got;
}
/* Resampler C API */
@@ -237,10 +322,8 @@ cubeb_resampler *
cubeb_resampler_create(cubeb_stream * stream,
cubeb_stream_params * input_params,
cubeb_stream_params * output_params,
- unsigned int target_rate,
- cubeb_data_callback callback,
- void * user_ptr,
- cubeb_resampler_quality quality)
+ unsigned int target_rate, cubeb_data_callback callback,
+ void * user_ptr, cubeb_resampler_quality quality)
{
cubeb_sample_format format;
@@ -252,38 +335,28 @@ cubeb_resampler_create(cubeb_stream * stream,
format = output_params->format;
}
- switch(format) {
- case CUBEB_SAMPLE_S16NE:
- return cubeb_resampler_create_internal<short>(stream,
- input_params,
- output_params,
- target_rate,
- callback,
- user_ptr,
- quality);
- case CUBEB_SAMPLE_FLOAT32NE:
- return cubeb_resampler_create_internal<float>(stream,
- input_params,
- output_params,
- target_rate,
- callback,
- user_ptr,
- quality);
- default:
- assert(false);
- return nullptr;
+ switch (format) {
+ case CUBEB_SAMPLE_S16NE:
+ return cubeb_resampler_create_internal<short>(stream, input_params,
+ output_params, target_rate,
+ callback, user_ptr, quality);
+ case CUBEB_SAMPLE_FLOAT32NE:
+ return cubeb_resampler_create_internal<float>(stream, input_params,
+ output_params, target_rate,
+ callback, user_ptr, quality);
+ default:
+ assert(false);
+ return nullptr;
}
}
long
-cubeb_resampler_fill(cubeb_resampler * resampler,
- void * input_buffer,
- long * input_frames_count,
- void * output_buffer,
+cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer,
+ long * input_frames_count, void * output_buffer,
long output_frames_needed)
{
- return resampler->fill(input_buffer, input_frames_count,
- output_buffer, output_frames_needed);
+ return resampler->fill(input_buffer, input_frames_count, output_buffer,
+ output_frames_needed);
}
void
diff --git a/media/libcubeb/src/cubeb_resampler.h b/media/libcubeb/src/cubeb_resampler.h
index 020ccc17ab..e9b95324ef 100644
--- a/media/libcubeb/src/cubeb_resampler.h
+++ b/media/libcubeb/src/cubeb_resampler.h
@@ -25,20 +25,26 @@ typedef enum {
* Create a resampler to adapt the requested sample rate into something that
* is accepted by the audio backend.
* @param stream A cubeb_stream instance supplied to the data callback.
- * @param params Used to calculate bytes per frame and buffer size for resampling.
- * @param target_rate The sampling rate after resampling.
+ * @param input_params Used to calculate bytes per frame and buffer size for
+ * resampling of the input side of the stream. NULL if input should not be
+ * resampled.
+ * @param output_params Used to calculate bytes per frame and buffer size for
+ * resampling of the output side of the stream. NULL if output should not be
+ * resampled.
+ * @param target_rate The sampling rate after resampling for the input side of
+ * the stream, and/or the sampling rate prior to resampling of the output side
+ * of the stream.
* @param callback A callback to request data for resampling.
* @param user_ptr User data supplied to the data callback.
* @param quality Quality of the resampler.
* @retval A non-null pointer if success.
*/
-cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream,
- cubeb_stream_params * input_params,
- cubeb_stream_params * output_params,
- unsigned int target_rate,
- cubeb_data_callback callback,
- void * user_ptr,
- cubeb_resampler_quality quality);
+cubeb_resampler *
+cubeb_resampler_create(cubeb_stream * stream,
+ cubeb_stream_params * input_params,
+ cubeb_stream_params * output_params,
+ unsigned int target_rate, cubeb_data_callback callback,
+ void * user_ptr, cubeb_resampler_quality quality);
/**
* Fill the buffer with frames acquired using the data callback. Resampling will
@@ -47,29 +53,30 @@ cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream,
* @param input_buffer A buffer of input samples
* @param input_frame_count The size of the buffer. Returns the number of frames
* consumed.
- * @param buffer The buffer to be filled.
- * @param frames_needed Number of frames that should be produced.
+ * @param output_buffer The buffer to be filled.
+ * @param output_frames_needed Number of frames that should be produced.
* @retval Number of frames that are actually produced.
* @retval CUBEB_ERROR on error.
*/
-long cubeb_resampler_fill(cubeb_resampler * resampler,
- void * input_buffer,
- long * input_frame_count,
- void * output_buffer,
- long output_frames_needed);
+long
+cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer,
+ long * input_frame_count, void * output_buffer,
+ long output_frames_needed);
/**
* Destroy a cubeb_resampler.
* @param resampler A cubeb_resampler instance.
*/
-void cubeb_resampler_destroy(cubeb_resampler * resampler);
+void
+cubeb_resampler_destroy(cubeb_resampler * resampler);
/**
* Returns the latency, in frames, of the resampler.
* @param resampler A cubeb resampler instance.
* @retval The latency, in frames, induced by the resampler.
*/
-long cubeb_resampler_latency(cubeb_resampler * resampler);
+long
+cubeb_resampler_latency(cubeb_resampler * resampler);
#if defined(__cplusplus)
}
diff --git a/media/libcubeb/src/cubeb_resampler_internal.h b/media/libcubeb/src/cubeb_resampler_internal.h
index 3c37a04b9c..2eabb7c4c4 100644
--- a/media/libcubeb/src/cubeb_resampler_internal.h
+++ b/media/libcubeb/src/cubeb_resampler_internal.h
@@ -8,9 +8,9 @@
#if !defined(CUBEB_RESAMPLER_INTERNAL)
#define CUBEB_RESAMPLER_INTERNAL
-#include <cmath>
-#include <cassert>
#include <algorithm>
+#include <cassert>
+#include <cmath>
#include <memory>
#ifdef CUBEB_GECKO_BUILD
#include "mozilla/UniquePtr.h"
@@ -25,21 +25,32 @@
#define MOZ_END_STD_NAMESPACE }
#endif
MOZ_BEGIN_STD_NAMESPACE
- using mozilla::DefaultDelete;
- using mozilla::UniquePtr;
- #define default_delete DefaultDelete
- #define unique_ptr UniquePtr
+using mozilla::DefaultDelete;
+using mozilla::UniquePtr;
+#define default_delete DefaultDelete
+#define unique_ptr UniquePtr
MOZ_END_STD_NAMESPACE
#endif
-#include "cubeb/cubeb.h"
-#include "cubeb_utils.h"
#include "cubeb-speex-resampler.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_log.h"
#include "cubeb_resampler.h"
+#include "cubeb_utils.h"
#include <stdio.h>
-/* This header file contains the internal C++ API of the resamplers, for testing. */
+/* This header file contains the internal C++ API of the resamplers, for
+ * testing. */
-int to_speex_quality(cubeb_resampler_quality q);
+// When dropping audio input frames to prevent building
+// an input delay, this function returns the number of frames
+// to keep in the buffer.
+// @parameter sample_rate The sample rate of the stream.
+// @return A number of frames to keep.
+uint32_t
+min_buffered_audio_frame(uint32_t sample_rate);
+
+int
+to_speex_quality(cubeb_resampler_quality q);
struct cubeb_resampler {
virtual long fill(void * input_buffer, long * input_frames_count,
@@ -48,62 +59,62 @@ struct cubeb_resampler {
virtual ~cubeb_resampler() {}
};
-class noop_resampler : public cubeb_resampler {
+/** Base class for processors. This is just used to share methods for now. */
+class processor {
public:
- noop_resampler(cubeb_stream * s,
- cubeb_data_callback cb,
- void * ptr)
- : stream(s)
- , data_callback(cb)
- , user_ptr(ptr)
+ explicit processor(uint32_t channels) : channels(channels) {}
+
+protected:
+ size_t frames_to_samples(size_t frames) const { return frames * channels; }
+ size_t samples_to_frames(size_t samples) const
{
+ assert(!(samples % channels));
+ return samples / channels;
}
+ /** The number of channel of the audio buffers to be resampled. */
+ const uint32_t channels;
+};
+
+template <typename T>
+class passthrough_resampler : public cubeb_resampler, public processor {
+public:
+ passthrough_resampler(cubeb_stream * s, cubeb_data_callback cb, void * ptr,
+ uint32_t input_channels, uint32_t sample_rate);
virtual long fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long output_frames);
- virtual long latency()
+ virtual long latency() { return 0; }
+
+ void drop_audio_if_needed()
{
- return 0;
+ uint32_t to_keep = min_buffered_audio_frame(sample_rate);
+ uint32_t available = samples_to_frames(internal_input_buffer.length());
+ if (available > to_keep) {
+ internal_input_buffer.pop(nullptr,
+ frames_to_samples(available - to_keep));
+ }
}
private:
cubeb_stream * const stream;
const cubeb_data_callback data_callback;
void * const user_ptr;
-};
-
-/** Base class for processors. This is just used to share methods for now. */
-class processor {
-public:
- explicit processor(uint32_t channels)
- : channels(channels)
- {}
-protected:
- size_t frames_to_samples(size_t frames)
- {
- return frames * channels;
- }
- size_t samples_to_frames(size_t samples)
- {
- assert(!(samples % channels));
- return samples / channels;
- }
- /** The number of channel of the audio buffers to be resampled. */
- const uint32_t channels;
+ /* This allows to buffer some input to account for the fact that we buffer
+ * some inputs. */
+ auto_array<T> internal_input_buffer;
+ uint32_t sample_rate;
};
/** Bidirectional resampler, can resample an input and an output stream, or just
* an input stream or output stream. In this case a delay is inserted in the
* opposite direction to keep the streams synchronized. */
-template<typename T, typename InputProcessing, typename OutputProcessing>
+template <typename T, typename InputProcessing, typename OutputProcessing>
class cubeb_resampler_speex : public cubeb_resampler {
public:
cubeb_resampler_speex(InputProcessing * input_processor,
- OutputProcessing * output_processor,
- cubeb_stream * s,
- cubeb_data_callback cb,
- void * ptr);
+ OutputProcessing * output_processor, cubeb_stream * s,
+ cubeb_data_callback cb, void * ptr);
virtual ~cubeb_resampler_speex();
@@ -123,7 +134,9 @@ public:
}
private:
- typedef long(cubeb_resampler_speex::*processing_callback)(T * input_buffer, long * input_frames_count, T * output_buffer, long output_frames_needed);
+ typedef long (cubeb_resampler_speex::*processing_callback)(
+ T * input_buffer, long * input_frames_count, T * output_buffer,
+ long output_frames_needed);
long fill_internal_duplex(T * input_buffer, long * input_frames_count,
T * output_buffer, long output_frames_needed);
@@ -138,14 +151,14 @@ private:
cubeb_stream * const stream;
const cubeb_data_callback data_callback;
void * const user_ptr;
+ bool draining = false;
};
/** Handles one way of a (possibly) duplex resampler, working on interleaved
* audio buffers of type T. This class is designed so that the number of frames
* coming out of the resampler can be precisely controled. It manages its own
* input buffer, and can use the caller's output buffer, or allocate its own. */
-template<typename T>
-class cubeb_resampler_speex_one_way : public processor {
+template <typename T> class cubeb_resampler_speex_one_way : public processor {
public:
/** The sample type of this resampler, either 16-bit integers or 32-bit
* floats. */
@@ -157,19 +170,26 @@ public:
* @parameter target_rate The sample-rate of the audio output.
* @parameter quality A number between 0 (fast, low quality) and 10 (slow,
* high quality). */
- cubeb_resampler_speex_one_way(uint32_t channels,
- uint32_t source_rate,
- uint32_t target_rate,
- int quality)
- : processor(channels)
- , resampling_ratio(static_cast<float>(source_rate) / target_rate)
- , additional_latency(0)
- , leftover_samples(0)
+ cubeb_resampler_speex_one_way(uint32_t channels, uint32_t source_rate,
+ uint32_t target_rate, int quality)
+ : processor(channels),
+ resampling_ratio(static_cast<float>(source_rate) / target_rate),
+ source_rate(source_rate), additional_latency(0), leftover_samples(0)
{
int r;
- speex_resampler = speex_resampler_init(channels, source_rate,
- target_rate, quality, &r);
+ speex_resampler =
+ speex_resampler_init(channels, source_rate, target_rate, quality, &r);
assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
+
+ uint32_t input_latency = speex_resampler_get_input_latency(speex_resampler);
+ const size_t LATENCY_SAMPLES = 8192;
+ T input_buffer[LATENCY_SAMPLES] = {};
+ T output_buffer[LATENCY_SAMPLES] = {};
+ uint32_t input_frame_count = input_latency;
+ uint32_t output_frame_count = LATENCY_SAMPLES;
+ assert(input_latency * channels <= LATENCY_SAMPLES);
+ speex_resample(input_buffer, &input_frame_count, output_buffer,
+ &output_frame_count);
}
/** Destructor, deallocate the resampler */
@@ -178,17 +198,6 @@ public:
speex_resampler_destroy(speex_resampler);
}
- /** Sometimes, it is necessary to add latency on one way of a two-way
- * resampler so that the stream are synchronized. This must be called only on
- * a fresh resampler, otherwise, silent samples will be inserted in the
- * stream.
- * @param frames the number of frames of latency to add. */
- void add_latency(size_t frames)
- {
- additional_latency += frames;
- resampling_in_buffer.push_silence(frames_to_samples(frames));
- }
-
/* Fill the resampler with `input_frame_count` frames. */
void input(T * input_buffer, size_t input_frame_count)
{
@@ -197,14 +206,14 @@ public:
}
/** Outputs exactly `output_frame_count` into `output_buffer`.
- * `output_buffer` has to be at least `output_frame_count` long. */
+ * `output_buffer` has to be at least `output_frame_count` long. */
size_t output(T * output_buffer, size_t output_frame_count)
{
uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
uint32_t out_len = output_frame_count;
- speex_resample(resampling_in_buffer.data(), &in_len,
- output_buffer, &out_len);
+ speex_resample(resampling_in_buffer.data(), &in_len, output_buffer,
+ &out_len);
/* This shifts back any unresampled samples to the beginning of the input
buffer. */
@@ -215,15 +224,17 @@ public:
size_t output_for_input(uint32_t input_frames)
{
- return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length()))
- / resampling_ratio);
+ return (size_t)floorf(
+ (input_frames + samples_to_frames(resampling_in_buffer.length())) /
+ resampling_ratio);
}
/** Returns a buffer containing exactly `output_frame_count` resampled frames.
- * The consumer should not hold onto the pointer. */
- T * output(size_t output_frame_count)
+ * The consumer should not hold onto the pointer. */
+ T * output(size_t output_frame_count, size_t * input_frames_used)
{
- if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) {
+ if (resampling_out_buffer.capacity() <
+ frames_to_samples(output_frame_count)) {
resampling_out_buffer.reserve(frames_to_samples(output_frame_count));
}
@@ -233,11 +244,21 @@ public:
speex_resample(resampling_in_buffer.data(), &in_len,
resampling_out_buffer.data(), &out_len);
- assert(out_len == output_frame_count);
+ if (out_len < output_frame_count) {
+ LOGV("underrun during resampling: got %u frames, expected %zu",
+ (unsigned)out_len, output_frame_count);
+ // silence the rightmost part
+ T * data = resampling_out_buffer.data();
+ for (uint32_t i = frames_to_samples(out_len);
+ i < frames_to_samples(output_frame_count); i++) {
+ data[i] = 0;
+ }
+ }
/* This shifts back any unresampled samples to the beginning of the input
buffer. */
resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
+ *input_frames_used = in_len;
return resampling_out_buffer.data();
}
@@ -249,8 +270,8 @@ public:
* only consider a single channel here so it's the same number of frames. */
int latency = 0;
- latency =
- speex_resampler_get_output_latency(speex_resampler) + additional_latency;
+ latency = speex_resampler_get_output_latency(speex_resampler) +
+ additional_latency;
assert(latency >= 0);
@@ -261,13 +282,16 @@ public:
* exactly `output_frame_count` resampled frames. This can return a number
* slightly bigger than what is strictly necessary, but it guaranteed that the
* number of output frames will be exactly equal. */
- uint32_t input_needed_for_output(uint32_t output_frame_count)
+ uint32_t input_needed_for_output(int32_t output_frame_count) const
{
- int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length());
- int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length());
+ assert(output_frame_count >= 0); // Check overflow
+ int32_t unresampled_frames_left =
+ samples_to_frames(resampling_in_buffer.length());
+ int32_t resampled_frames_left =
+ samples_to_frames(resampling_out_buffer.length());
float input_frames_needed =
- (output_frame_count - unresampled_frames_left) * resampling_ratio
- - resampled_frames_left;
+ (output_frame_count - unresampled_frames_left) * resampling_ratio -
+ resampled_frames_left;
if (input_frames_needed < 0) {
return 0;
}
@@ -294,9 +318,20 @@ public:
resampling_in_buffer.set_length(leftover_samples +
frames_to_samples(written_frames));
}
+
+ void drop_audio_if_needed()
+ {
+ // Keep at most 100ms buffered.
+ uint32_t available = samples_to_frames(resampling_in_buffer.length());
+ uint32_t to_keep = min_buffered_audio_frame(source_rate);
+ if (available > to_keep) {
+ resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep));
+ }
+ }
+
private:
/** Wrapper for the speex resampling functions to have a typed
- * interface. */
+ * interface. */
void speex_resample(float * input_buffer, uint32_t * input_frame_count,
float * output_buffer, uint32_t * output_frame_count)
{
@@ -304,11 +339,9 @@ private:
int rv;
rv =
#endif
- speex_resampler_process_interleaved_float(speex_resampler,
- input_buffer,
- input_frame_count,
- output_buffer,
- output_frame_count);
+ speex_resampler_process_interleaved_float(
+ speex_resampler, input_buffer, input_frame_count, output_buffer,
+ output_frame_count);
assert(rv == RESAMPLER_ERR_SUCCESS);
}
@@ -319,17 +352,16 @@ private:
int rv;
rv =
#endif
- speex_resampler_process_interleaved_int(speex_resampler,
- input_buffer,
- input_frame_count,
- output_buffer,
- output_frame_count);
+ speex_resampler_process_interleaved_int(
+ speex_resampler, input_buffer, input_frame_count, output_buffer,
+ output_frame_count);
assert(rv == RESAMPLER_ERR_SUCCESS);
}
/** The state for the speex resampler used internaly. */
SpeexResamplerState * speex_resampler;
/** Source rate / target rate. */
const float resampling_ratio;
+ const uint32_t source_rate;
/** Storage for the input frames, to be resampled. Also contains
* any unresampled frames after resampling. */
auto_array<T> resampling_in_buffer;
@@ -343,27 +375,20 @@ private:
};
/** This class allows delaying an audio stream by `frames` frames. */
-template<typename T>
-class delay_line : public processor {
+template <typename T> class delay_line : public processor {
public:
/** Constructor
* @parameter frames the number of frames of delay.
- * @parameter channels the number of channels of this delay line. */
- delay_line(uint32_t frames, uint32_t channels)
- : processor(channels)
- , length(frames)
- , leftover_samples(0)
+ * @parameter channels the number of channels of this delay line.
+ * @parameter sample_rate sample-rate of the audio going through this delay
+ * line */
+ delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate)
+ : processor(channels), length(frames), leftover_samples(0),
+ sample_rate(sample_rate)
{
/* Fill the delay line with some silent frames to add latency. */
delay_input_buffer.push_silence(frames * channels);
}
- /* Add some latency to the delay line.
- * @param frames the number of frames of latency to add. */
- void add_latency(size_t frames)
- {
- length += frames;
- delay_input_buffer.push_silence(frames_to_samples(frames));
- }
/** Push some frames into the delay line.
* @parameter buffer the frames to push.
* @parameter frame_count the number of frames in #buffer. */
@@ -375,7 +400,7 @@ public:
* @parameter frames_needed the number of frames to be returned.
* @return a buffer containing the delayed frames. The consumer should not
* hold onto the pointer. */
- T * output(uint32_t frames_needed)
+ T * output(uint32_t frames_needed, size_t * input_frames_used)
{
if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
delay_output_buffer.reserve(frames_to_samples(frames_needed));
@@ -385,6 +410,7 @@ public:
delay_output_buffer.push(delay_input_buffer.data(),
frames_to_samples(frames_needed));
delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
+ *input_frames_used = frames_needed;
return delay_output_buffer.data();
}
@@ -396,7 +422,8 @@ public:
T * input_buffer(uint32_t frames_needed)
{
leftover_samples = delay_input_buffer.length();
- delay_input_buffer.reserve(leftover_samples + frames_to_samples(frames_needed));
+ delay_input_buffer.reserve(leftover_samples +
+ frames_to_samples(frames_needed));
return delay_input_buffer.data() + leftover_samples;
}
/** This method works with `input_buffer`, and allows to inform the processor
@@ -426,21 +453,27 @@ public:
* @parameter frames_needed the number of frames one want to write into the
* delay_line
* @returns the number of frames one will get. */
- size_t input_needed_for_output(uint32_t frames_needed)
+ uint32_t input_needed_for_output(int32_t frames_needed) const
{
+ assert(frames_needed >= 0); // Check overflow
return frames_needed;
}
- /** Returns the number of frames produces for `input_frames` frames in input */
- size_t output_for_input(uint32_t input_frames)
- {
- return input_frames;
- }
+ /** Returns the number of frames produces for `input_frames` frames in input
+ */
+ size_t output_for_input(uint32_t input_frames) { return input_frames; }
/** The number of frames this delay line delays the stream by.
* @returns The number of frames of delay. */
- size_t latency()
+ size_t latency() { return length; }
+
+ void drop_audio_if_needed()
{
- return length;
+ size_t available = samples_to_frames(delay_input_buffer.length());
+ uint32_t to_keep = min_buffered_audio_frame(sample_rate);
+ if (available > to_keep) {
+ delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
+ }
}
+
private:
/** The length, in frames, of this delay line */
uint32_t length;
@@ -452,17 +485,17 @@ private:
/** The output buffer. This is only ever used if using the ::output with a
* single argument. */
auto_array<T> delay_output_buffer;
+ uint32_t sample_rate;
};
/** This sits behind the C API and is more typed. */
-template<typename T>
+template <typename T>
cubeb_resampler *
cubeb_resampler_create_internal(cubeb_stream * stream,
cubeb_stream_params * input_params,
cubeb_stream_params * output_params,
unsigned int target_rate,
- cubeb_data_callback callback,
- void * user_ptr,
+ cubeb_data_callback callback, void * user_ptr,
cubeb_resampler_quality quality)
{
std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr;
@@ -477,31 +510,31 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
sample rate, use a no-op resampler, that simply forwards the buffers to the
callback. */
if (((input_params && input_params->rate == target_rate) &&
- (output_params && output_params->rate == target_rate)) ||
+ (output_params && output_params->rate == target_rate)) ||
(input_params && !output_params && (input_params->rate == target_rate)) ||
- (output_params && !input_params && (output_params->rate == target_rate))) {
- return new noop_resampler(stream, callback, user_ptr);
+ (output_params && !input_params &&
+ (output_params->rate == target_rate))) {
+ LOG("Input and output sample-rate match, target rate of %dHz", target_rate);
+ return new passthrough_resampler<T>(
+ stream, callback, user_ptr, input_params ? input_params->channels : 0,
+ target_rate);
}
/* Determine if we need to resampler one or both directions, and create the
resamplers. */
if (output_params && (output_params->rate != target_rate)) {
- output_resampler.reset(
- new cubeb_resampler_speex_one_way<T>(output_params->channels,
- target_rate,
- output_params->rate,
- to_speex_quality(quality)));
+ output_resampler.reset(new cubeb_resampler_speex_one_way<T>(
+ output_params->channels, target_rate, output_params->rate,
+ to_speex_quality(quality)));
if (!output_resampler) {
return NULL;
}
}
if (input_params && (input_params->rate != target_rate)) {
- input_resampler.reset(
- new cubeb_resampler_speex_one_way<T>(input_params->channels,
- input_params->rate,
- target_rate,
- to_speex_quality(quality)));
+ input_resampler.reset(new cubeb_resampler_speex_one_way<T>(
+ input_params->channels, input_params->rate, target_rate,
+ to_speex_quality(quality)));
if (!input_resampler) {
return NULL;
}
@@ -512,39 +545,42 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
* other direction so that the streams are synchronized. */
if (input_resampler && !output_resampler && input_params && output_params) {
output_delay.reset(new delay_line<T>(input_resampler->latency(),
- output_params->channels));
+ output_params->channels,
+ output_params->rate));
if (!output_delay) {
return NULL;
}
- } else if (output_resampler && !input_resampler && input_params && output_params) {
+ } else if (output_resampler && !input_resampler && input_params &&
+ output_params) {
input_delay.reset(new delay_line<T>(output_resampler->latency(),
- input_params->channels));
+ input_params->channels,
+ output_params->rate));
if (!input_delay) {
return NULL;
}
}
if (input_resampler && output_resampler) {
- return new cubeb_resampler_speex<T,
- cubeb_resampler_speex_one_way<T>,
- cubeb_resampler_speex_one_way<T>>
- (input_resampler.release(),
- output_resampler.release(),
- stream, callback, user_ptr);
+ LOG("Resampling input (%d) and output (%d) to target rate of %dHz",
+ input_params->rate, output_params->rate, target_rate);
+ return new cubeb_resampler_speex<T, cubeb_resampler_speex_one_way<T>,
+ cubeb_resampler_speex_one_way<T>>(
+ input_resampler.release(), output_resampler.release(), stream, callback,
+ user_ptr);
} else if (input_resampler) {
- return new cubeb_resampler_speex<T,
- cubeb_resampler_speex_one_way<T>,
- delay_line<T>>
- (input_resampler.release(),
- output_delay.release(),
- stream, callback, user_ptr);
+ LOG("Resampling input (%d) to target and output rate of %dHz",
+ input_params->rate, target_rate);
+ return new cubeb_resampler_speex<T, cubeb_resampler_speex_one_way<T>,
+ delay_line<T>>(input_resampler.release(),
+ output_delay.release(),
+ stream, callback, user_ptr);
} else {
- return new cubeb_resampler_speex<T,
- delay_line<T>,
- cubeb_resampler_speex_one_way<T>>
- (input_delay.release(),
- output_resampler.release(),
- stream, callback, user_ptr);
+ LOG("Resampling output (%dHz) to target and input rate of %dHz",
+ output_params->rate, target_rate);
+ return new cubeb_resampler_speex<T, delay_line<T>,
+ cubeb_resampler_speex_one_way<T>>(
+ input_delay.release(), output_resampler.release(), stream, callback,
+ user_ptr);
}
}
diff --git a/media/libcubeb/src/cubeb_ring_array.h b/media/libcubeb/src/cubeb_ring_array.h
index 51b3b321a3..05a8fe9620 100644
--- a/media/libcubeb/src/cubeb_ring_array.h
+++ b/media/libcubeb/src/cubeb_ring_array.h
@@ -16,17 +16,16 @@
them in the correct order. */
typedef struct {
- AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated space for the buffers. */
- unsigned int tail; /**< Index of the last element (first to deliver). */
- unsigned int count; /**< Number of elements in the array. */
- unsigned int capacity; /**< Total length of the array. */
+ AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated
+ space for the buffers. */
+ unsigned int tail; /**< Index of the last element (first to deliver). */
+ unsigned int count; /**< Number of elements in the array. */
+ unsigned int capacity; /**< Total length of the array. */
} ring_array;
static int
-single_audiobuffer_init(AudioBuffer * buffer,
- uint32_t bytesPerFrame,
- uint32_t channelsPerFrame,
- uint32_t frames)
+single_audiobuffer_init(AudioBuffer * buffer, uint32_t bytesPerFrame,
+ uint32_t channelsPerFrame, uint32_t frames)
{
assert(buffer);
assert(bytesPerFrame > 0 && channelsPerFrame && frames > 0);
@@ -36,7 +35,7 @@ single_audiobuffer_init(AudioBuffer * buffer,
if (buffer->mData == NULL) {
return CUBEB_ERROR;
}
- PodZero(static_cast<char*>(buffer->mData), size);
+ PodZero(static_cast<char *>(buffer->mData), size);
buffer->mNumberChannels = channelsPerFrame;
buffer->mDataByteSize = size;
@@ -48,15 +47,12 @@ single_audiobuffer_init(AudioBuffer * buffer,
@param ra The ring_array pointer of allocated structure.
@retval 0 on success. */
int
-ring_array_init(ring_array * ra,
- uint32_t capacity,
- uint32_t bytesPerFrame,
- uint32_t channelsPerFrame,
- uint32_t framesPerBuffer)
+ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame,
+ uint32_t channelsPerFrame, uint32_t framesPerBuffer)
{
assert(ra);
- if (capacity == 0 || bytesPerFrame == 0 ||
- channelsPerFrame == 0 || framesPerBuffer == 0) {
+ if (capacity == 0 || bytesPerFrame == 0 || channelsPerFrame == 0 ||
+ framesPerBuffer == 0) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
ra->capacity = capacity;
@@ -70,8 +66,7 @@ ring_array_init(ring_array * ra,
}
for (unsigned int i = 0; i < ra->capacity; ++i) {
- if (single_audiobuffer_init(&ra->buffer_array[i],
- bytesPerFrame,
+ if (single_audiobuffer_init(&ra->buffer_array[i], bytesPerFrame,
channelsPerFrame,
framesPerBuffer) != CUBEB_OK) {
return CUBEB_ERROR;
@@ -87,7 +82,7 @@ void
ring_array_destroy(ring_array * ra)
{
assert(ra);
- if (ra->buffer_array == NULL){
+ if (ra->buffer_array == NULL) {
return;
}
for (unsigned int i = 0; i < ra->capacity; ++i) {
@@ -95,12 +90,13 @@ ring_array_destroy(ring_array * ra)
operator delete(ra->buffer_array[i].mData);
}
}
- delete [] ra->buffer_array;
+ delete[] ra->buffer_array;
}
/** Get the allocated buffer to be stored with fresh data.
@param ra The ring_array pointer.
- @retval Pointer of the allocated space to be stored with fresh data or NULL if full. */
+ @retval Pointer of the allocated space to be stored with fresh data or NULL
+ if full. */
AudioBuffer *
ring_array_get_free_buffer(ring_array * ra)
{
@@ -156,4 +152,4 @@ ring_array_get_dummy_buffer(ring_array * ra)
return &ra->buffer_array[0];
}
-#endif //CUBEB_RING_ARRAY_H
+#endif // CUBEB_RING_ARRAY_H
diff --git a/media/libcubeb/src/cubeb_ringbuffer.h b/media/libcubeb/src/cubeb_ringbuffer.h
new file mode 100644
index 0000000000..28381849fc
--- /dev/null
+++ b/media/libcubeb/src/cubeb_ringbuffer.h
@@ -0,0 +1,468 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_RING_BUFFER_H
+#define CUBEB_RING_BUFFER_H
+
+#include "cubeb_utils.h"
+#include <algorithm>
+#include <atomic>
+#include <cstdint>
+#include <memory>
+#include <thread>
+
+/**
+ * Single producer single consumer lock-free and wait-free ring buffer.
+ *
+ * This data structure allows producing data from one thread, and consuming it
+ * on another thread, safely and without explicit synchronization. If used on
+ * two threads, this data structure uses atomics for thread safety. It is
+ * possible to disable the use of atomics at compile time and only use this data
+ * structure on one thread.
+ *
+ * The role for the producer and the consumer must be constant, i.e., the
+ * producer should always be on one thread and the consumer should always be on
+ * another thread.
+ *
+ * Some words about the inner workings of this class:
+ * - Capacity is fixed. Only one allocation is performed, in the constructor.
+ * When reading and writing, the return value of the method allows checking if
+ * the ring buffer is empty or full.
+ * - We always keep the read index at least one element ahead of the write
+ * index, so we can distinguish between an empty and a full ring buffer: an
+ * empty ring buffer is when the write index is at the same position as the
+ * read index. A full buffer is when the write index is exactly one position
+ * before the read index.
+ * - We synchronize updates to the read index after having read the data, and
+ * the write index after having written the data. This means that the each
+ * thread can only touch a portion of the buffer that is not touched by the
+ * other thread.
+ * - Callers are expected to provide buffers. When writing to the queue,
+ * elements are copied into the internal storage from the buffer passed in.
+ * When reading from the queue, the user is expected to provide a buffer.
+ * Because this is a ring buffer, data might not be contiguous in memory,
+ * providing an external buffer to copy into is an easy way to have linear
+ * data for further processing.
+ */
+template <typename T> class ring_buffer_base {
+public:
+ /**
+ * Constructor for a ring buffer.
+ *
+ * This performs an allocation, but is the only allocation that will happen
+ * for the life time of a `ring_buffer_base`.
+ *
+ * @param capacity The maximum number of element this ring buffer will hold.
+ */
+ ring_buffer_base(int capacity)
+ /* One more element to distinguish from empty and full buffer. */
+ : capacity_(capacity + 1)
+ {
+ assert(storage_capacity() < std::numeric_limits<int>::max() / 2 &&
+ "buffer too large for the type of index used.");
+ assert(capacity_ > 0);
+
+ data_.reset(new T[storage_capacity()]);
+ /* If this queue is using atomics, initializing those members as the last
+ * action in the constructor acts as a full barrier, and allow capacity() to
+ * be thread-safe. */
+ write_index_ = 0;
+ read_index_ = 0;
+ }
+ /**
+ * Push `count` zero or default constructed elements in the array.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param count The number of elements to enqueue.
+ * @return The number of element enqueued.
+ */
+ int enqueue_default(int count) { return enqueue(nullptr, count); }
+ /**
+ * @brief Put an element in the queue
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param element The element to put in the queue.
+ *
+ * @return 1 if the element was inserted, 0 otherwise.
+ */
+ int enqueue(T & element) { return enqueue(&element, 1); }
+ /**
+ * Push `count` elements in the ring buffer.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param elements a pointer to a buffer containing at least `count` elements.
+ * If `elements` is nullptr, zero or default constructed elements are
+ * enqueued.
+ * @param count The number of elements to read from `elements`
+ * @return The number of elements successfully coped from `elements` and
+ * inserted into the ring buffer.
+ */
+ int enqueue(T * elements, int count)
+ {
+#ifndef NDEBUG
+ assert_correct_thread(producer_id);
+#endif
+
+ int rd_idx = read_index_.load(std::memory_order_relaxed);
+ int wr_idx = write_index_.load(std::memory_order_relaxed);
+
+ if (full_internal(rd_idx, wr_idx)) {
+ return 0;
+ }
+
+ int to_write = std::min(available_write_internal(rd_idx, wr_idx), count);
+
+ /* First part, from the write index to the end of the array. */
+ int first_part = std::min(storage_capacity() - wr_idx, to_write);
+ /* Second part, from the beginning of the array */
+ int second_part = to_write - first_part;
+
+ if (elements) {
+ Copy(data_.get() + wr_idx, elements, first_part);
+ Copy(data_.get(), elements + first_part, second_part);
+ } else {
+ ConstructDefault(data_.get() + wr_idx, first_part);
+ ConstructDefault(data_.get(), second_part);
+ }
+
+ write_index_.store(increment_index(wr_idx, to_write),
+ std::memory_order_release);
+
+ return to_write;
+ }
+ /**
+ * Retrieve at most `count` elements from the ring buffer, and copy them to
+ * `elements`, if non-null.
+ *
+ * Only safely called on the consumer side.
+ *
+ * @param elements A pointer to a buffer with space for at least `count`
+ * elements. If `elements` is `nullptr`, `count` element will be discarded.
+ * @param count The maximum number of elements to dequeue.
+ * @return The number of elements written to `elements`.
+ */
+ int dequeue(T * elements, int count)
+ {
+#ifndef NDEBUG
+ assert_correct_thread(consumer_id);
+#endif
+
+ int wr_idx = write_index_.load(std::memory_order_acquire);
+ int rd_idx = read_index_.load(std::memory_order_relaxed);
+
+ if (empty_internal(rd_idx, wr_idx)) {
+ return 0;
+ }
+
+ int to_read = std::min(available_read_internal(rd_idx, wr_idx), count);
+
+ int first_part = std::min(storage_capacity() - rd_idx, to_read);
+ int second_part = to_read - first_part;
+
+ if (elements) {
+ Copy(elements, data_.get() + rd_idx, first_part);
+ Copy(elements + first_part, data_.get(), second_part);
+ }
+
+ read_index_.store(increment_index(rd_idx, to_read),
+ std::memory_order_relaxed);
+
+ return to_read;
+ }
+ /**
+ * Get the number of available element for consuming.
+ *
+ * Only safely called on the consumer thread.
+ *
+ * @return The number of available elements for reading.
+ */
+ int available_read() const
+ {
+#ifndef NDEBUG
+ assert_correct_thread(consumer_id);
+#endif
+ return available_read_internal(
+ read_index_.load(std::memory_order_relaxed),
+ write_index_.load(std::memory_order_relaxed));
+ }
+ /**
+ * Get the number of available elements for consuming.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @return The number of empty slots in the buffer, available for writing.
+ */
+ int available_write() const
+ {
+#ifndef NDEBUG
+ assert_correct_thread(producer_id);
+#endif
+ return available_write_internal(
+ read_index_.load(std::memory_order_relaxed),
+ write_index_.load(std::memory_order_relaxed));
+ }
+ /**
+ * Get the total capacity, for this ring buffer.
+ *
+ * Can be called safely on any thread.
+ *
+ * @return The maximum capacity of this ring buffer.
+ */
+ int capacity() const { return storage_capacity() - 1; }
+ /**
+ * Reset the consumer and producer thread identifier, in case the thread are
+ * being changed. This has to be externally synchronized. This is no-op when
+ * asserts are disabled.
+ */
+ void reset_thread_ids()
+ {
+#ifndef NDEBUG
+ consumer_id = producer_id = std::thread::id();
+#endif
+ }
+
+private:
+ /** Return true if the ring buffer is empty.
+ *
+ * @param read_index the read index to consider
+ * @param write_index the write index to consider
+ * @return true if the ring buffer is empty, false otherwise.
+ **/
+ bool empty_internal(int read_index, int write_index) const
+ {
+ return write_index == read_index;
+ }
+ /** Return true if the ring buffer is full.
+ *
+ * This happens if the write index is exactly one element behind the read
+ * index.
+ *
+ * @param read_index the read index to consider
+ * @param write_index the write index to consider
+ * @return true if the ring buffer is full, false otherwise.
+ **/
+ bool full_internal(int read_index, int write_index) const
+ {
+ return (write_index + 1) % storage_capacity() == read_index;
+ }
+ /**
+ * Return the size of the storage. It is one more than the number of elements
+ * that can be stored in the buffer.
+ *
+ * @return the number of elements that can be stored in the buffer.
+ */
+ int storage_capacity() const { return capacity_; }
+ /**
+ * Returns the number of elements available for reading.
+ *
+ * @return the number of available elements for reading.
+ */
+ int available_read_internal(int read_index, int write_index) const
+ {
+ if (write_index >= read_index) {
+ return write_index - read_index;
+ } else {
+ return write_index + storage_capacity() - read_index;
+ }
+ }
+ /**
+ * Returns the number of empty elements, available for writing.
+ *
+ * @return the number of elements that can be written into the array.
+ */
+ int available_write_internal(int read_index, int write_index) const
+ {
+ /* We substract one element here to always keep at least one sample
+ * free in the buffer, to distinguish between full and empty array. */
+ int rv = read_index - write_index - 1;
+ if (write_index >= read_index) {
+ rv += storage_capacity();
+ }
+ return rv;
+ }
+ /**
+ * Increments an index, wrapping it around the storage.
+ *
+ * @param index a reference to the index to increment.
+ * @param increment the number by which `index` is incremented.
+ * @return the new index.
+ */
+ int increment_index(int index, int increment) const
+ {
+ assert(increment >= 0);
+ return (index + increment) % storage_capacity();
+ }
+ /**
+ * @brief This allows checking that enqueue (resp. dequeue) are always called
+ * by the right thread.
+ *
+ * @param id the id of the thread that has called the calling method first.
+ */
+#ifndef NDEBUG
+ static void assert_correct_thread(std::thread::id & id)
+ {
+ if (id == std::thread::id()) {
+ id = std::this_thread::get_id();
+ return;
+ }
+ assert(id == std::this_thread::get_id());
+ }
+#endif
+ /** Index at which the oldest element is at, in samples. */
+ std::atomic<int> read_index_;
+ /** Index at which to write new elements. `write_index` is always at
+ * least one element ahead of `read_index_`. */
+ std::atomic<int> write_index_;
+ /** Maximum number of elements that can be stored in the ring buffer. */
+ const int capacity_;
+ /** Data storage */
+ std::unique_ptr<T[]> data_;
+#ifndef NDEBUG
+ /** The id of the only thread that is allowed to read from the queue. */
+ mutable std::thread::id consumer_id;
+ /** The id of the only thread that is allowed to write from the queue. */
+ mutable std::thread::id producer_id;
+#endif
+};
+
+/**
+ * Adapter for `ring_buffer_base` that exposes an interface in frames.
+ */
+template <typename T> class audio_ring_buffer_base {
+public:
+ /**
+ * @brief Constructor.
+ *
+ * @param channel_count Number of channels.
+ * @param capacity_in_frames The capacity in frames.
+ */
+ audio_ring_buffer_base(int channel_count, int capacity_in_frames)
+ : channel_count(channel_count),
+ ring_buffer(frames_to_samples(capacity_in_frames))
+ {
+ assert(channel_count > 0);
+ }
+ /**
+ * @brief Enqueue silence.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param frame_count The number of frames of silence to enqueue.
+ * @return The number of frames of silence actually written to the queue.
+ */
+ int enqueue_default(int frame_count)
+ {
+ return samples_to_frames(
+ ring_buffer.enqueue(nullptr, frames_to_samples(frame_count)));
+ }
+ /**
+ * @brief Enqueue `frames_count` frames of audio.
+ *
+ * Only safely called from the producer thread.
+ *
+ * @param [in] frames If non-null, the frames to enqueue.
+ * Otherwise, silent frames are enqueued.
+ * @param frame_count The number of frames to enqueue.
+ *
+ * @return The number of frames enqueued
+ */
+
+ int enqueue(T * frames, int frame_count)
+ {
+ return samples_to_frames(
+ ring_buffer.enqueue(frames, frames_to_samples(frame_count)));
+ }
+
+ /**
+ * @brief Removes `frame_count` frames from the buffer, and
+ * write them to `frames` if it is non-null.
+ *
+ * Only safely called on the consumer thread.
+ *
+ * @param frames If non-null, the frames are copied to `frames`.
+ * Otherwise, they are dropped.
+ * @param frame_count The number of frames to remove.
+ *
+ * @return The number of frames actually dequeud.
+ */
+ int dequeue(T * frames, int frame_count)
+ {
+ return samples_to_frames(
+ ring_buffer.dequeue(frames, frames_to_samples(frame_count)));
+ }
+ /**
+ * Get the number of available frames of audio for consuming.
+ *
+ * Only safely called on the consumer thread.
+ *
+ * @return The number of available frames of audio for reading.
+ */
+ int available_read() const
+ {
+ return samples_to_frames(ring_buffer.available_read());
+ }
+ /**
+ * Get the number of available frames of audio for consuming.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @return The number of empty slots in the buffer, available for writing.
+ */
+ int available_write() const
+ {
+ return samples_to_frames(ring_buffer.available_write());
+ }
+ /**
+ * Get the total capacity, for this ring buffer.
+ *
+ * Can be called safely on any thread.
+ *
+ * @return The maximum capacity of this ring buffer.
+ */
+ int capacity() const { return samples_to_frames(ring_buffer.capacity()); }
+
+private:
+ /**
+ * @brief Frames to samples conversion.
+ *
+ * @param frames The number of frames.
+ *
+ * @return A number of samples.
+ */
+ int frames_to_samples(int frames) const { return frames * channel_count; }
+ /**
+ * @brief Samples to frames conversion.
+ *
+ * @param samples The number of samples.
+ *
+ * @return A number of frames.
+ */
+ int samples_to_frames(int samples) const { return samples / channel_count; }
+ /** Number of channels of audio that will stream through this ring buffer. */
+ int channel_count;
+ /** The underlying ring buffer that is used to store the data. */
+ ring_buffer_base<T> ring_buffer;
+};
+
+/**
+ * Lock-free instantiation of the `ring_buffer_base` type. This is safe to use
+ * from two threads, one producer, one consumer (that never change role),
+ * without explicit synchronization.
+ */
+template <typename T> using lock_free_queue = ring_buffer_base<T>;
+/**
+ * Lock-free instantiation of the `audio_ring_buffer` type. This is safe to use
+ * from two threads, one producer, one consumer (that never change role),
+ * without explicit synchronization.
+ */
+template <typename T>
+using lock_free_audio_ring_buffer = audio_ring_buffer_base<T>;
+
+#endif // CUBEB_RING_BUFFER_H
diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c
index c7ac184465..d7fb79202e 100644
--- a/media/libcubeb/src/cubeb_sndio.c
+++ b/media/libcubeb/src/cubeb_sndio.c
@@ -4,56 +4,109 @@
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include <assert.h>
+#include <dlfcn.h>
+#include <inttypes.h>
#include <math.h>
#include <poll.h>
#include <pthread.h>
#include <sndio.h>
#include <stdbool.h>
-#include <stdlib.h>
#include <stdio.h>
-#include <assert.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
+#include <stdlib.h>
#if defined(CUBEB_SNDIO_DEBUG)
#define DPR(...) fprintf(stderr, __VA_ARGS__);
#else
-#define DPR(...) do {} while(0)
+#define DPR(...) \
+ do { \
+ } while (0)
+#endif
+
+#ifdef DISABLE_LIBSNDIO_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) cubeb_##x
+#define LIBSNDIO_API_VISIT(X) \
+ X(sio_close) \
+ X(sio_eof) \
+ X(sio_getpar) \
+ X(sio_initpar) \
+ X(sio_nfds) \
+ X(sio_onmove) \
+ X(sio_open) \
+ X(sio_pollfd) \
+ X(sio_read) \
+ X(sio_revents) \
+ X(sio_setpar) \
+ X(sio_start) \
+ X(sio_stop) \
+ X(sio_write)
+
+#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
+LIBSNDIO_API_VISIT(MAKE_TYPEDEF);
+#undef MAKE_TYPEDEF
#endif
static struct cubeb_ops const sndio_ops;
struct cubeb {
struct cubeb_ops const * ops;
+ void * libsndio;
};
struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context;
- pthread_t th; /* to run real-time audio i/o */
- pthread_mutex_t mtx; /* protects hdl and pos */
- struct sio_hdl *hdl; /* link us to sndio */
- int active; /* cubec_start() called */
- int conv; /* need float->s16 conversion */
- unsigned char *buf; /* data is prepared here */
- unsigned int nfr; /* number of frames in buf */
- unsigned int bpf; /* bytes per frame */
- unsigned int pchan; /* number of play channels */
- uint64_t rdpos; /* frame number Joe hears right now */
- uint64_t wrpos; /* number of written frames */
- cubeb_data_callback data_cb; /* cb to preapare data */
- cubeb_state_callback state_cb; /* cb to notify about state changes */
- void *arg; /* user arg to {data,state}_cb */
+ void * arg; /* user arg to {data,state}_cb */
+ /**/
+ pthread_t th; /* to run real-time audio i/o */
+ pthread_mutex_t mtx; /* protects hdl and pos */
+ struct sio_hdl * hdl; /* link us to sndio */
+ int mode; /* bitmap of SIO_{PLAY,REC} */
+ int active; /* cubec_start() called */
+ int conv; /* need float->s16 conversion */
+ unsigned char * rbuf; /* rec data consumed from here */
+ unsigned char * pbuf; /* play data is prepared here */
+ unsigned int nfr; /* number of frames in ibuf and obuf */
+ unsigned int rbpf; /* rec bytes per frame */
+ unsigned int pbpf; /* play bytes per frame */
+ unsigned int rchan; /* number of rec channels */
+ unsigned int pchan; /* number of play channels */
+ unsigned int nblks; /* number of blocks in the buffer */
+ uint64_t hwpos; /* frame number Joe hears right now */
+ uint64_t swpos; /* number of frames produced/consumed */
+ cubeb_data_callback data_cb; /* cb to preapare data */
+ cubeb_state_callback state_cb; /* cb to notify about state changes */
+ float volume; /* current volume */
};
static void
-float_to_s16(void *ptr, long nsamp)
+s16_setvol(void * ptr, long nsamp, float volume)
{
- int16_t *dst = ptr;
- float *src = ptr;
+ int16_t * dst = ptr;
+ int32_t mult = volume * 32768;
+ int32_t s;
+
+ while (nsamp-- > 0) {
+ s = *dst;
+ s = (s * mult) >> 15;
+ *(dst++) = s;
+ }
+}
+
+static void
+float_to_s16(void * ptr, long nsamp, float volume)
+{
+ int16_t * dst = ptr;
+ float * src = ptr;
+ float mult = volume * 32768;
int s;
while (nsamp-- > 0) {
- s = lrintf(*(src++) * 32768);
+ s = lrintf(*(src++) * mult);
if (s < -32768)
s = -32768;
else if (s > 32767)
@@ -63,61 +116,146 @@ float_to_s16(void *ptr, long nsamp)
}
static void
-sndio_onmove(void *arg, int delta)
+s16_to_float(void * ptr, long nsamp)
{
- cubeb_stream *s = (cubeb_stream *)arg;
+ int16_t * src = ptr;
+ float * dst = ptr;
- s->rdpos += delta * s->bpf;
+ src += nsamp;
+ dst += nsamp;
+ while (nsamp-- > 0)
+ *(--dst) = (1. / 32768) * *(--src);
+}
+
+static const char *
+sndio_get_device()
+{
+#ifdef __linux__
+ /*
+ * On other platforms default to sndio devices,
+ * so cubebs other backends can be used instead.
+ */
+ const char * dev = getenv("AUDIODEVICE");
+ if (dev == NULL || *dev == '\0')
+ return "snd/0";
+ return dev;
+#else
+ return SIO_DEVANY;
+#endif
+}
+
+static void
+sndio_onmove(void * arg, int delta)
+{
+ cubeb_stream * s = (cubeb_stream *)arg;
+
+ s->hwpos += delta;
}
static void *
-sndio_mainloop(void *arg)
+sndio_mainloop(void * arg)
{
-#define MAXFDS 8
- struct pollfd pfds[MAXFDS];
- cubeb_stream *s = arg;
- int n, nfds, revents, state = CUBEB_STATE_STARTED;
- size_t start = 0, end = 0;
+ struct pollfd * pfds;
+ cubeb_stream * s = arg;
+ int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED;
+ size_t pstart = 0, pend = 0, rstart = 0, rend = 0;
long nfr;
+ nfds = WRAP(sio_nfds)(s->hdl);
+ pfds = calloc(nfds, sizeof(struct pollfd));
+ if (pfds == NULL)
+ return NULL;
+
DPR("sndio_mainloop()\n");
s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
pthread_mutex_lock(&s->mtx);
- if (!sio_start(s->hdl)) {
+ if (!WRAP(sio_start)(s->hdl)) {
pthread_mutex_unlock(&s->mtx);
+ free(pfds);
return NULL;
}
DPR("sndio_mainloop(), started\n");
- start = end = s->nfr;
+ if (s->mode & SIO_PLAY) {
+ pstart = pend = s->nfr * s->pbpf;
+ prime = s->nblks;
+ if (s->mode & SIO_REC) {
+ memset(s->rbuf, 0, s->nfr * s->rbpf);
+ rstart = rend = s->nfr * s->rbpf;
+ }
+ } else {
+ prime = 0;
+ rstart = 0;
+ rend = s->nfr * s->rbpf;
+ }
+
for (;;) {
if (!s->active) {
DPR("sndio_mainloop() stopped\n");
state = CUBEB_STATE_STOPPED;
break;
}
- if (start == end) {
- if (end < s->nfr) {
+
+ /* do we have a complete block? */
+ if ((!(s->mode & SIO_PLAY) || pstart == pend) &&
+ (!(s->mode & SIO_REC) || rstart == rend)) {
+
+ if (eof) {
DPR("sndio_mainloop() drained\n");
state = CUBEB_STATE_DRAINED;
break;
}
+
+ if ((s->mode & SIO_REC) && s->conv)
+ s16_to_float(s->rbuf, s->nfr * s->rchan);
+
+ /* invoke call-back, it returns less that s->nfr if done */
pthread_mutex_unlock(&s->mtx);
- nfr = s->data_cb(s, s->arg, NULL, s->buf, s->nfr);
+ nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr);
pthread_mutex_lock(&s->mtx);
if (nfr < 0) {
DPR("sndio_mainloop() cb err\n");
state = CUBEB_STATE_ERROR;
break;
}
- if (s->conv)
- float_to_s16(s->buf, nfr * s->pchan);
- start = 0;
- end = nfr * s->bpf;
+ s->swpos += nfr;
+
+ /* was this last call-back invocation (aka end-of-stream) ? */
+ if (nfr < s->nfr) {
+
+ if (!(s->mode & SIO_PLAY) || nfr == 0) {
+ state = CUBEB_STATE_DRAINED;
+ break;
+ }
+
+ /* need to write (aka drain) the partial play block we got */
+ pend = nfr * s->pbpf;
+ eof = 1;
+ }
+
+ if (prime > 0)
+ prime--;
+
+ if (s->mode & SIO_PLAY) {
+ if (s->conv)
+ float_to_s16(s->pbuf, nfr * s->pchan, s->volume);
+ else
+ s16_setvol(s->pbuf, nfr * s->pchan, s->volume);
+ }
+
+ if (s->mode & SIO_REC)
+ rstart = 0;
+ if (s->mode & SIO_PLAY)
+ pstart = 0;
}
- if (end == 0)
- continue;
- nfds = sio_pollfd(s->hdl, pfds, POLLOUT);
+
+ events = 0;
+ if ((s->mode & SIO_REC) && rstart < rend && prime == 0)
+ events |= POLLIN;
+ if ((s->mode & SIO_PLAY) && pstart < pend)
+ events |= POLLOUT;
+ nfds = WRAP(sio_pollfd)(s->hdl, pfds, events);
+
if (nfds > 0) {
pthread_mutex_unlock(&s->mtx);
n = poll(pfds, nfds, -1);
@@ -125,88 +263,165 @@ sndio_mainloop(void *arg)
if (n < 0)
continue;
}
- revents = sio_revents(s->hdl, pfds);
- if (revents & POLLHUP)
+
+ revents = WRAP(sio_revents)(s->hdl, pfds);
+
+ if (revents & POLLHUP) {
+ state = CUBEB_STATE_ERROR;
break;
+ }
+
if (revents & POLLOUT) {
- n = sio_write(s->hdl, s->buf + start, end - start);
- if (n == 0) {
+ n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart);
+ if (n == 0 && WRAP(sio_eof)(s->hdl)) {
DPR("sndio_mainloop() werr\n");
state = CUBEB_STATE_ERROR;
break;
}
- s->wrpos += n;
- start += n;
+ pstart += n;
}
+
+ if (revents & POLLIN) {
+ n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart);
+ if (n == 0 && WRAP(sio_eof)(s->hdl)) {
+ DPR("sndio_mainloop() rerr\n");
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ rstart += n;
+ }
+
+ /* skip rec block, if not recording (yet) */
+ if (prime > 0 && (s->mode & SIO_REC))
+ rstart = rend;
}
- sio_stop(s->hdl);
- s->rdpos = s->wrpos;
+ WRAP(sio_stop)(s->hdl);
+ s->hwpos = s->swpos;
pthread_mutex_unlock(&s->mtx);
s->state_cb(s, s->arg, state);
+ free(pfds);
return NULL;
}
/*static*/ int
-sndio_init(cubeb **context, char const *context_name)
+sndio_init(cubeb ** context, char const * context_name)
{
+ void * libsndio = NULL;
+ struct sio_hdl * hdl;
+
+ assert(context);
+
+#ifndef DISABLE_LIBSNDIO_DLOPEN
+ libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY);
+ if (!libsndio) {
+ libsndio = dlopen("libsndio.so", RTLD_LAZY);
+ if (!libsndio) {
+ DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name);
+ return CUBEB_ERROR;
+ }
+ }
+
+#define LOAD(x) \
+ { \
+ cubeb_##x = dlsym(libsndio, #x); \
+ if (!cubeb_##x) { \
+ DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \
+ dlclose(libsndio); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBSNDIO_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
+ /* test if sndio works */
+ hdl = WRAP(sio_open)(sndio_get_device(), SIO_PLAY, 1);
+ if (hdl == NULL) {
+ return CUBEB_ERROR;
+ }
+ WRAP(sio_close)(hdl);
+
DPR("sndio_init(%s)\n", context_name);
- *context = malloc(sizeof(*context));
+ *context = malloc(sizeof(**context));
+ if (*context == NULL)
+ return CUBEB_ERROR;
+ (*context)->libsndio = libsndio;
(*context)->ops = &sndio_ops;
(void)context_name;
return CUBEB_OK;
}
static char const *
-sndio_get_backend_id(cubeb *context)
+sndio_get_backend_id(cubeb * context)
{
return "sndio";
}
static void
-sndio_destroy(cubeb *context)
+sndio_destroy(cubeb * context)
{
DPR("sndio_destroy()\n");
+ if (context->libsndio)
+ dlclose(context->libsndio);
free(context);
}
static int
-sndio_stream_init(cubeb * context,
- cubeb_stream ** stream,
- char const * stream_name,
- cubeb_devid input_device,
+sndio_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
unsigned int latency_frames,
cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void *user_ptr)
+ cubeb_state_callback state_callback, void * user_ptr)
{
- cubeb_stream *s;
+ cubeb_stream * s;
struct sio_par wpar, rpar;
- DPR("sndio_stream_init(%s)\n", stream_name);
- size_t size;
+ cubeb_sample_format format;
+ int rate;
+ size_t bps;
- assert(!input_stream_params && "not supported.");
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
- }
+ DPR("sndio_stream_init(%s)\n", stream_name);
s = malloc(sizeof(cubeb_stream));
if (s == NULL)
return CUBEB_ERROR;
+ memset(s, 0, sizeof(cubeb_stream));
+ s->mode = 0;
+ if (input_stream_params) {
+ if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ DPR("sndio_stream_init(), loopback not supported\n");
+ goto err;
+ }
+ s->mode |= SIO_REC;
+ format = input_stream_params->format;
+ rate = input_stream_params->rate;
+ }
+ if (output_stream_params) {
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ DPR("sndio_stream_init(), loopback not supported\n");
+ goto err;
+ }
+ s->mode |= SIO_PLAY;
+ format = output_stream_params->format;
+ rate = output_stream_params->rate;
+ }
+ if (s->mode == 0) {
+ DPR("sndio_stream_init(), neither playing nor recording\n");
+ goto err;
+ }
s->context = context;
- s->hdl = sio_open(NULL, SIO_PLAY, 1);
+ s->hdl = WRAP(sio_open)(sndio_get_device(), s->mode, 1);
if (s->hdl == NULL) {
- free(s);
DPR("sndio_stream_init(), sio_open() failed\n");
- return CUBEB_ERROR;
+ goto err;
}
- sio_initpar(&wpar);
+ WRAP(sio_initpar)(&wpar);
wpar.sig = 1;
wpar.bits = 16;
- switch (output_stream_params->format) {
+ switch (format) {
case CUBEB_SAMPLE_S16LE:
wpar.le = 1;
break;
@@ -218,53 +433,70 @@ sndio_stream_init(cubeb * context,
break;
default:
DPR("sndio_stream_init() unsupported format\n");
- return CUBEB_ERROR_INVALID_FORMAT;
+ goto err;
}
- wpar.rate = output_stream_params->rate;
- wpar.pchan = output_stream_params->channels;
+ wpar.rate = rate;
+ if (s->mode & SIO_REC)
+ wpar.rchan = input_stream_params->channels;
+ if (s->mode & SIO_PLAY)
+ wpar.pchan = output_stream_params->channels;
wpar.appbufsz = latency_frames;
- if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) {
- sio_close(s->hdl);
- free(s);
+ if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) {
DPR("sndio_stream_init(), sio_setpar() failed\n");
- return CUBEB_ERROR;
+ goto err;
}
- if (rpar.bits != wpar.bits || rpar.le != wpar.le ||
- rpar.sig != wpar.sig || rpar.rate != wpar.rate ||
- rpar.pchan != wpar.pchan) {
- sio_close(s->hdl);
- free(s);
+ if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig ||
+ rpar.rate != wpar.rate ||
+ ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) ||
+ ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) {
DPR("sndio_stream_init() unsupported params\n");
- return CUBEB_ERROR_INVALID_FORMAT;
+ goto err;
}
- sio_onmove(s->hdl, sndio_onmove, s);
+ WRAP(sio_onmove)(s->hdl, sndio_onmove, s);
s->active = 0;
s->nfr = rpar.round;
- s->bpf = rpar.bps * rpar.pchan;
+ s->rbpf = rpar.bps * rpar.rchan;
+ s->pbpf = rpar.bps * rpar.pchan;
+ s->rchan = rpar.rchan;
s->pchan = rpar.pchan;
+ s->nblks = rpar.bufsz / rpar.round;
s->data_cb = data_callback;
s->state_cb = state_callback;
s->arg = user_ptr;
s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
- s->rdpos = s->wrpos = 0;
- if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
+ s->hwpos = s->swpos = 0;
+ if (format == CUBEB_SAMPLE_FLOAT32LE) {
s->conv = 1;
- size = rpar.round * rpar.pchan * sizeof(float);
+ bps = sizeof(float);
} else {
s->conv = 0;
- size = rpar.round * rpar.pchan * rpar.bps;
+ bps = rpar.bps;
}
- s->buf = malloc(size);
- if (s->buf == NULL) {
- sio_close(s->hdl);
- free(s);
- return CUBEB_ERROR;
+ if (s->mode & SIO_PLAY) {
+ s->pbuf = malloc(bps * rpar.pchan * rpar.round);
+ if (s->pbuf == NULL)
+ goto err;
+ }
+ if (s->mode & SIO_REC) {
+ s->rbuf = malloc(bps * rpar.rchan * rpar.round);
+ if (s->rbuf == NULL)
+ goto err;
}
+ s->volume = 1.;
*stream = s;
DPR("sndio_stream_init() end, ok\n");
(void)context;
(void)stream_name;
return CUBEB_OK;
+err:
+ if (s->hdl)
+ WRAP(sio_close)(s->hdl);
+ if (s->pbuf)
+ free(s->pbuf);
+ if (s->rbuf)
+ free(s->pbuf);
+ free(s);
+ return CUBEB_ERROR;
}
static int
@@ -280,31 +512,41 @@ sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
static int
sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
{
- // XXX Not yet implemented.
- *rate = 44100;
-
+ /*
+ * We've no device-independent prefered rate; any rate will work if
+ * sndiod is running. If it isn't, 48kHz is what is most likely to
+ * work as most (but not all) devices support it.
+ */
+ *rate = 48000;
return CUBEB_OK;
}
static int
-sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
+sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
{
- // XXX Not yet implemented.
+ /*
+ * We've no device-independent minimum latency.
+ */
*latency_frames = 2048;
return CUBEB_OK;
}
static void
-sndio_stream_destroy(cubeb_stream *s)
+sndio_stream_destroy(cubeb_stream * s)
{
DPR("sndio_stream_destroy()\n");
- sio_close(s->hdl);
+ WRAP(sio_close)(s->hdl);
+ if (s->mode & SIO_PLAY)
+ free(s->pbuf);
+ if (s->mode & SIO_REC)
+ free(s->rbuf);
free(s);
}
static int
-sndio_stream_start(cubeb_stream *s)
+sndio_stream_start(cubeb_stream * s)
{
int err;
@@ -319,9 +561,9 @@ sndio_stream_start(cubeb_stream *s)
}
static int
-sndio_stream_stop(cubeb_stream *s)
+sndio_stream_stop(cubeb_stream * s)
{
- void *dummy;
+ void * dummy;
DPR("sndio_stream_stop()\n");
if (s->active) {
@@ -332,21 +574,25 @@ sndio_stream_stop(cubeb_stream *s)
}
static int
-sndio_stream_get_position(cubeb_stream *s, uint64_t *p)
+sndio_stream_get_position(cubeb_stream * s, uint64_t * p)
{
pthread_mutex_lock(&s->mtx);
- DPR("sndio_stream_get_position() %lld\n", s->rdpos);
- *p = s->rdpos / s->bpf;
+ DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos);
+ *p = s->hwpos;
pthread_mutex_unlock(&s->mtx);
return CUBEB_OK;
}
static int
-sndio_stream_set_volume(cubeb_stream *s, float volume)
+sndio_stream_set_volume(cubeb_stream * s, float volume)
{
DPR("sndio_stream_set_volume(%f)\n", volume);
pthread_mutex_lock(&s->mtx);
- sio_setvol(s->hdl, SIO_MAXVOL * volume);
+ if (volume < 0.)
+ volume = 0.;
+ else if (volume > 1.0)
+ volume = 1.;
+ s->volume = volume;
pthread_mutex_unlock(&s->mtx);
return CUBEB_OK;
}
@@ -356,28 +602,68 @@ sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
// http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open
// in the "Measuring the latency and buffers usage" paragraph.
- *latency = (stm->wrpos - stm->rdpos) / stm->bpf;
+ *latency = stm->swpos - stm->hwpos;
+ return CUBEB_OK;
+}
+
+static int
+sndio_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ static char dev[] = SIO_DEVANY;
+ cubeb_device_info * device;
+
+ device = malloc(sizeof(cubeb_device_info));
+ if (device == NULL)
+ return CUBEB_ERROR;
+
+ device->devid = dev; /* passed to stream_init() */
+ device->device_id = dev; /* printable in UI */
+ device->friendly_name = dev; /* same, but friendly */
+ device->group_id = dev; /* actual device if full-duplex */
+ device->vendor_name = NULL; /* may be NULL */
+ device->type = type; /* Input/Output */
+ device->state = CUBEB_DEVICE_STATE_ENABLED;
+ device->preferred = CUBEB_DEVICE_PREF_ALL;
+ device->format = CUBEB_DEVICE_FMT_S16NE;
+ device->default_format = CUBEB_DEVICE_FMT_S16NE;
+ device->max_channels = (type == CUBEB_DEVICE_TYPE_INPUT) ? 2 : 8;
+ device->default_rate = 48000;
+ device->min_rate = 4000;
+ device->max_rate = 192000;
+ device->latency_lo = 480;
+ device->latency_hi = 9600;
+ collection->device = device;
+ collection->count = 1;
+ return CUBEB_OK;
+}
+
+static int
+sndio_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ free(collection->device);
return CUBEB_OK;
}
static struct cubeb_ops const sndio_ops = {
- .init = sndio_init,
- .get_backend_id = sndio_get_backend_id,
- .get_max_channel_count = sndio_get_max_channel_count,
- .get_min_latency = sndio_get_min_latency,
- .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
- .enumerate_devices = NULL,
- .destroy = sndio_destroy,
- .stream_init = sndio_stream_init,
- .stream_destroy = sndio_stream_destroy,
- .stream_start = sndio_stream_start,
- .stream_stop = sndio_stream_stop,
- .stream_get_position = sndio_stream_get_position,
- .stream_get_latency = sndio_stream_get_latency,
- .stream_set_volume = sndio_stream_set_volume,
- .stream_set_panning = NULL,
- .stream_get_current_device = NULL,
- .stream_device_destroy = NULL,
- .stream_register_device_changed_callback = NULL,
- .register_device_collection_changed = NULL
-};
+ .init = sndio_init,
+ .get_backend_id = sndio_get_backend_id,
+ .get_max_channel_count = sndio_get_max_channel_count,
+ .get_min_latency = sndio_get_min_latency,
+ .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
+ .enumerate_devices = sndio_enumerate_devices,
+ .device_collection_destroy = sndio_device_collection_destroy,
+ .destroy = sndio_destroy,
+ .stream_init = sndio_stream_init,
+ .stream_destroy = sndio_stream_destroy,
+ .stream_start = sndio_stream_start,
+ .stream_stop = sndio_stream_stop,
+ .stream_get_position = sndio_stream_get_position,
+ .stream_get_latency = sndio_stream_get_latency,
+ .stream_set_volume = sndio_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/media/libcubeb/src/cubeb_strings.c b/media/libcubeb/src/cubeb_strings.c
new file mode 100644
index 0000000000..5fe5b791e2
--- /dev/null
+++ b/media/libcubeb/src/cubeb_strings.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "cubeb_strings.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CUBEB_STRINGS_INLINE_COUNT 4
+
+struct cubeb_strings {
+ uint32_t size;
+ uint32_t count;
+ char ** data;
+ char * small_store[CUBEB_STRINGS_INLINE_COUNT];
+};
+
+int
+cubeb_strings_init(cubeb_strings ** strings)
+{
+ cubeb_strings * strs = NULL;
+
+ if (!strings) {
+ return CUBEB_ERROR;
+ }
+
+ strs = calloc(1, sizeof(cubeb_strings));
+ assert(strs);
+
+ if (!strs) {
+ return CUBEB_ERROR;
+ }
+
+ strs->size = sizeof(strs->small_store) / sizeof(strs->small_store[0]);
+ strs->count = 0;
+ strs->data = strs->small_store;
+
+ *strings = strs;
+
+ return CUBEB_OK;
+}
+
+void
+cubeb_strings_destroy(cubeb_strings * strings)
+{
+ char ** sp = NULL;
+ char ** se = NULL;
+
+ if (!strings) {
+ return;
+ }
+
+ sp = strings->data;
+ se = sp + strings->count;
+
+ for (; sp != se; sp++) {
+ if (*sp) {
+ free(*sp);
+ }
+ }
+
+ if (strings->data != strings->small_store) {
+ free(strings->data);
+ }
+
+ free(strings);
+}
+
+/** Look for string in string storage.
+ @param strings Opaque pointer to interned string storage.
+ @param s String to look up.
+ @retval Read-only string or NULL if not found. */
+static char const *
+cubeb_strings_lookup(cubeb_strings * strings, char const * s)
+{
+ char ** sp = NULL;
+ char ** se = NULL;
+
+ if (!strings || !s) {
+ return NULL;
+ }
+
+ sp = strings->data;
+ se = sp + strings->count;
+
+ for (; sp != se; sp++) {
+ if (*sp && strcmp(*sp, s) == 0) {
+ return *sp;
+ }
+ }
+
+ return NULL;
+}
+
+static char const *
+cubeb_strings_push(cubeb_strings * strings, char const * s)
+{
+ char * is = NULL;
+
+ if (strings->count == strings->size) {
+ char ** new_data;
+ uint32_t value_size = sizeof(char const *);
+ uint32_t new_size = strings->size * 2;
+ if (!new_size || value_size > (uint32_t)-1 / new_size) {
+ // overflow
+ return NULL;
+ }
+
+ if (strings->small_store == strings->data) {
+ // First time heap allocation.
+ new_data = malloc(new_size * value_size);
+ if (new_data) {
+ memcpy(new_data, strings->small_store, sizeof(strings->small_store));
+ }
+ } else {
+ new_data = realloc(strings->data, new_size * value_size);
+ }
+
+ if (!new_data) {
+ // out of memory
+ return NULL;
+ }
+
+ strings->size = new_size;
+ strings->data = new_data;
+ }
+
+ is = strdup(s);
+ strings->data[strings->count++] = is;
+
+ return is;
+}
+
+char const *
+cubeb_strings_intern(cubeb_strings * strings, char const * s)
+{
+ char const * is = NULL;
+
+ if (!strings || !s) {
+ return NULL;
+ }
+
+ is = cubeb_strings_lookup(strings, s);
+ if (is) {
+ return is;
+ }
+
+ return cubeb_strings_push(strings, s);
+}
diff --git a/media/libcubeb/src/cubeb_strings.h b/media/libcubeb/src/cubeb_strings.h
new file mode 100644
index 0000000000..cfffbbc68a
--- /dev/null
+++ b/media/libcubeb/src/cubeb_strings.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_STRINGS_H
+#define CUBEB_STRINGS_H
+
+#include "cubeb/cubeb.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/** Opaque handle referencing interned string storage. */
+typedef struct cubeb_strings cubeb_strings;
+
+/** Initialize an interned string structure.
+ @param strings An out param where an opaque pointer to the
+ interned string storage will be returned.
+ @retval CUBEB_OK in case of success.
+ @retval CUBEB_ERROR in case of error. */
+CUBEB_EXPORT int
+cubeb_strings_init(cubeb_strings ** strings);
+
+/** Destroy an interned string structure freeing all associated memory.
+ @param strings An opaque pointer to the interned string storage to
+ destroy. */
+CUBEB_EXPORT void
+cubeb_strings_destroy(cubeb_strings * strings);
+
+/** Add string to internal storage.
+ @param strings Opaque pointer to interned string storage.
+ @param s String to add to storage.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR
+ */
+CUBEB_EXPORT char const *
+cubeb_strings_intern(cubeb_strings * strings, char const * s);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // !CUBEB_STRINGS_H
diff --git a/media/libcubeb/src/cubeb_sun.c b/media/libcubeb/src/cubeb_sun.c
index b768bca561..3b7bef71d6 100644
--- a/media/libcubeb/src/cubeb_sun.c
+++ b/media/libcubeb/src/cubeb_sun.c
@@ -1,504 +1,733 @@
/*
- * Copyright (c) 2013, 2017 Ginn Chen <ginnchen@gmail.com>
+ * Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
-#include <poll.h>
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include <fcntl.h>
+#include <limits.h>
#include <pthread.h>
-#include <stdlib.h>
+#include <stdbool.h>
#include <stdio.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/audio.h>
-#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
#include <unistd.h>
-#include <sys/stropts.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-/* Macros copied from audio_oss.h */
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License (the "License").
- * You may not use this file except in compliance with the License.
- *
- * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
- * or http://www.opensolaris.org/os/licensing.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information: Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- */
+/* Default to 4 + 1 for the default device. */
+#ifndef SUN_DEVICE_COUNT
+#define SUN_DEVICE_COUNT (5)
+#endif
+
+/* Supported well by most hardware. */
+#ifndef SUN_PREFER_RATE
+#define SUN_PREFER_RATE (48000)
+#endif
+
+/* Standard acceptable minimum. */
+#ifndef SUN_LATENCY_MS
+#define SUN_LATENCY_MS (40)
+#endif
+
+#ifndef SUN_DEFAULT_DEVICE
+#define SUN_DEFAULT_DEVICE "/dev/audio"
+#endif
+
+#ifndef SUN_BUFFER_FRAMES
+#define SUN_BUFFER_FRAMES (32)
+#endif
+
/*
- * Copyright (C) 4Front Technologies 1996-2008.
- *
- * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
- * Use is subject to license terms.
+ * Supported on NetBSD regardless of hardware.
*/
-#define OSSIOCPARM_MASK 0x1fff /* parameters must be < 8192 bytes */
-#define OSSIOC_VOID 0x00000000 /* no parameters */
-#define OSSIOC_OUT 0x20000000 /* copy out parameters */
-#define OSSIOC_IN 0x40000000 /* copy in parameters */
-#define OSSIOC_INOUT (OSSIOC_IN|OSSIOC_OUT)
-#define OSSIOC_SZ(t) ((sizeof (t) & OSSIOCPARM_MASK) << 16)
-#define __OSSIO(x, y) ((int)(OSSIOC_VOID|(x<<8)|y))
-#define __OSSIOR(x, y, t) ((int)(OSSIOC_OUT|OSSIOC_SZ(t)|(x<<8)|y))
-#define __OSSIOWR(x, y, t) ((int)(OSSIOC_INOUT|OSSIOC_SZ(t)|(x<<8)|y))
-#define SNDCTL_DSP_SPEED __OSSIOWR('P', 2, int)
-#define SNDCTL_DSP_CHANNELS __OSSIOWR('P', 6, int)
-#define SNDCTL_DSP_SETFMT __OSSIOWR('P', 5, int) /* Selects ONE fmt */
-#define SNDCTL_DSP_GETODELAY __OSSIOR('P', 23, int)
-#define SNDCTL_DSP_HALT_OUTPUT __OSSIO('P', 34)
-#define AFMT_S16_LE 0x00000010
-#define AFMT_S16_BE 0x00000020
-
-#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
-#define AFMT_S16_NE AFMT_S16_BE
+
+#ifndef SUN_MAX_CHANNELS
+#ifdef __NetBSD__
+#define SUN_MAX_CHANNELS (12)
#else
-#define AFMT_S16_NE AFMT_S16_LE
+#define SUN_MAX_CHANNELS (2)
+#endif
#endif
-#define DEFAULT_AUDIO_DEVICE "/dev/audio"
-#define DEFAULT_DSP_DEVICE "/dev/dsp"
-
-#define BUF_SIZE_MS 10
+#ifndef SUN_MIN_RATE
+#define SUN_MIN_RATE (1000)
+#endif
-#if defined(CUBEB_SUNAUDIO_DEBUG)
-#define DPR(...) fprintf(stderr, __VA_ARGS__);
-#else
-#define DPR(...) do {} while(0)
+#ifndef SUN_MAX_RATE
+#define SUN_MAX_RATE (192000)
#endif
-static struct cubeb_ops const sunaudio_ops;
+static struct cubeb_ops const sun_ops;
struct cubeb {
struct cubeb_ops const * ops;
};
+struct sun_stream {
+ char name[32];
+ int fd;
+ void * buf;
+ struct audio_info info;
+ unsigned frame_size; /* precision in bytes * channels */
+ bool floating;
+};
+
struct cubeb_stream {
- cubeb * context;
- pthread_t th; /* to run real-time audio i/o */
- pthread_mutex_t mutex; /* protects fd and frm_played */
- int fd; /* link us to sunaudio */
- int active; /* cubec_start() called */
- int conv; /* need float->s16 conversion */
- int using_oss;
- unsigned char *buf; /* data is prepared here */
- unsigned int rate;
- unsigned int n_channles;
- unsigned int bytes_per_ch;
- unsigned int n_frm;
- unsigned int buffer_size;
- int64_t frm_played;
- cubeb_data_callback data_cb; /* cb to preapare data */
- cubeb_state_callback state_cb; /* cb to notify about state changes */
- void *arg; /* user arg to {data,state}_cb */
+ struct cubeb * context;
+ void * user_ptr;
+ pthread_t thread;
+ pthread_mutex_t mutex; /* protects running, volume, frames_written */
+ bool running;
+ float volume;
+ struct sun_stream play;
+ struct sun_stream record;
+ cubeb_data_callback data_cb;
+ cubeb_state_callback state_cb;
+ uint64_t frames_written;
+ uint64_t blocks_written;
};
+int
+sun_init(cubeb ** context, char const * context_name)
+{
+ cubeb * c;
+
+ (void)context_name;
+ if ((c = calloc(1, sizeof(cubeb))) == NULL) {
+ return CUBEB_ERROR;
+ }
+ c->ops = &sun_ops;
+ *context = c;
+ return CUBEB_OK;
+}
+
static void
-float_to_s16(void *ptr, long nsamp)
+sun_destroy(cubeb * context)
{
- int16_t *dst = ptr;
- float *src = ptr;
+ free(context);
+}
- while (nsamp-- > 0)
- *(dst++) = *(src++) * 32767;
+static char const *
+sun_get_backend_id(cubeb * context)
+{
+ return "sun";
}
-static void *
-sunaudio_mainloop(void *arg)
+static int
+sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
{
- struct cubeb_stream *s = arg;
- int state;
+ (void)context;
- DPR("sunaudio_mainloop()\n");
+ *rate = SUN_PREFER_RATE;
+ return CUBEB_OK;
+}
- s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
+static int
+sun_get_max_channel_count(cubeb * context, uint32_t * max_channels)
+{
+ (void)context;
- pthread_mutex_lock(&s->mutex);
- DPR("sunaudio_mainloop(), started\n");
+ *max_channels = SUN_MAX_CHANNELS;
+ return CUBEB_OK;
+}
- for (;;) {
- if (!s->active) {
- DPR("sunaudio_mainloop() stopped\n");
- state = CUBEB_STATE_STOPPED;
- break;
- }
+static int
+sun_get_min_latency(cubeb * context, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ (void)context;
- if (!s->using_oss) {
- audio_info_t info;
- ioctl(s->fd, AUDIO_GETINFO, &info);
- if (s->frm_played > info.play.samples + 3 * s->n_frm) {
- pthread_mutex_unlock(&s->mutex);
- struct timespec ts = {0, 10000}; // 10 ms
- nanosleep(&ts, NULL);
- pthread_mutex_lock(&s->mutex);
- continue;
- }
- }
+ *latency_frames = SUN_LATENCY_MS * params.rate / 1000;
+ return CUBEB_OK;
+}
- pthread_mutex_unlock(&s->mutex);
- unsigned int got = s->data_cb(s, s->arg, NULL, s->buf, s->n_frm);
- DPR("sunaudio_mainloop() ask %d got %d\n", s->n_frm, got);
- pthread_mutex_lock(&s->mutex);
+static int
+sun_get_hwinfo(const char * device, struct audio_info * format, int * props,
+ struct audio_device * dev)
+{
+ int fd = -1;
- if (got < 0) {
- DPR("sunaudio_mainloop() cb err\n");
- state = CUBEB_STATE_ERROR;
- break;
- }
+ if ((fd = open(device, O_RDONLY)) == -1) {
+ goto error;
+ }
+#ifdef AUDIO_GETFORMAT
+ if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) {
+ goto error;
+ }
+#endif
+#ifdef AUDIO_GETPROPS
+ if (ioctl(fd, AUDIO_GETPROPS, props) != 0) {
+ goto error;
+ }
+#endif
+ if (ioctl(fd, AUDIO_GETDEV, dev) != 0) {
+ goto error;
+ }
+ close(fd);
+ return CUBEB_OK;
+error:
+ if (fd != -1) {
+ close(fd);
+ }
+ return CUBEB_ERROR;
+}
- if (s->conv) {
- float_to_s16(s->buf, got * s->n_channles);
- }
+/*
+ * XXX: PR kern/54264
+ */
+static int
+sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)
+{
+ return prinfo->precision >= 8 && prinfo->precision <= 32 &&
+ prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS &&
+ prinfo->sample_rate < SUN_MAX_RATE &&
+ prinfo->sample_rate > SUN_MIN_RATE;
+}
- unsigned int avail = got * 2 * s->n_channles; // coverted to s16
- unsigned int pos = 0;
+static int
+sun_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ unsigned i;
+ cubeb_device_info device = {0};
+ char dev[16] = SUN_DEFAULT_DEVICE;
+ char dev_friendly[64];
+ struct audio_info hwfmt;
+ struct audio_device hwname;
+ struct audio_prinfo * prinfo = NULL;
+ int hwprops;
+
+ collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
+ if (collection->device == NULL) {
+ return CUBEB_ERROR;
+ }
+ collection->count = 0;
- while (avail > 0 && s->active) {
- int written = write(s->fd, s->buf + pos, avail);
- if (written == -1) {
- if (errno != EINTR && errno != EWOULDBLOCK) {
- DPR("sunaudio_mainloop() write err\n");
- state = CUBEB_STATE_ERROR;
- break;
- }
- pthread_mutex_unlock(&s->mutex);
- struct timespec ts = {0, 10000}; // 10 ms
- nanosleep(&ts, NULL);
- pthread_mutex_lock(&s->mutex);
- } else {
- pos += written;
- DPR("sunaudio_mainloop() write %d pos %d\n", written, pos);
- s->frm_played += written / 2 / s->n_channles;
- avail -= written;
- }
+ for (i = 0; i < SUN_DEVICE_COUNT; ++i) {
+ if (i > 0) {
+ (void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1);
}
-
- if ((got < s->n_frm)) {
- DPR("sunaudio_mainloop() drained\n");
- state = CUBEB_STATE_DRAINED;
+ if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) {
+ continue;
+ }
+#ifdef AUDIO_GETPROPS
+ device.type = 0;
+ if ((hwprops & AUDIO_PROP_CAPTURE) != 0 &&
+ sun_prinfo_verify_sanity(&hwfmt.record)) {
+ /* the device supports recording, probably */
+ device.type |= CUBEB_DEVICE_TYPE_INPUT;
+ }
+ if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 &&
+ sun_prinfo_verify_sanity(&hwfmt.play)) {
+ /* the device supports playback, probably */
+ device.type |= CUBEB_DEVICE_TYPE_OUTPUT;
+ }
+ switch (device.type) {
+ case 0:
+ /* device doesn't do input or output, aliens probably involved */
+ continue;
+ case CUBEB_DEVICE_TYPE_INPUT:
+ if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) {
+ /* this device is input only, not scanning for those, skip it */
+ continue;
+ }
break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) {
+ /* this device is output only, not scanning for those, skip it */
+ continue;
+ }
+ break;
+ }
+ if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) {
+ prinfo = &hwfmt.record;
+ }
+ if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) {
+ prinfo = &hwfmt.play;
}
+#endif
+ if (i > 0) {
+ (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)",
+ hwname.name, hwname.version, hwname.config, i - 1);
+ } else {
+ (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)",
+ hwname.name, hwname.version, hwname.config);
+ }
+ device.devid = (void *)(uintptr_t)i;
+ device.device_id = strdup(dev);
+ device.friendly_name = strdup(dev_friendly);
+ device.group_id = strdup(dev);
+ device.vendor_name = strdup(hwname.name);
+ device.type = type;
+ device.state = CUBEB_DEVICE_STATE_ENABLED;
+ device.preferred =
+ (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
+#ifdef AUDIO_GETFORMAT
+ device.max_channels = prinfo->channels;
+ device.default_rate = prinfo->sample_rate;
+#else
+ device.max_channels = 2;
+ device.default_rate = SUN_PREFER_RATE;
+#endif
+ device.default_format = CUBEB_DEVICE_FMT_S16NE;
+ device.format = CUBEB_DEVICE_FMT_S16NE;
+ device.min_rate = SUN_MIN_RATE;
+ device.max_rate = SUN_MAX_RATE;
+ device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000;
+ device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000;
+ collection->device[collection->count++] = device;
}
+ return CUBEB_OK;
+}
- pthread_mutex_unlock(&s->mutex);
- s->state_cb(s, s->arg, state);
+static int
+sun_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ unsigned i;
- return NULL;
+ for (i = 0; i < collection->count; ++i) {
+ free((char *)collection->device[i].device_id);
+ free((char *)collection->device[i].friendly_name);
+ free((char *)collection->device[i].group_id);
+ free((char *)collection->device[i].vendor_name);
+ }
+ free(collection->device);
+ return CUBEB_OK;
}
-/*static*/ int
-sunaudio_init(cubeb **context, char const *context_name)
+static int
+sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
+ struct audio_info * info, struct audio_prinfo * prinfo)
{
- DPR("sunaudio_init(%s)\n", context_name);
- *context = malloc(sizeof(*context));
- (*context)->ops = &sunaudio_ops;
- (void)context_name;
+ prinfo->channels = params->channels;
+ prinfo->sample_rate = params->rate;
+#ifdef AUDIO_ENCODING_SLINEAR_LE
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
+ prinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
+ prinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ prinfo->encoding = AUDIO_ENCODING_SLINEAR;
+ prinfo->precision = 32;
+ break;
+ default:
+ LOG("Unsupported format");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+#else
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16NE:
+ prinfo->encoding = AUDIO_ENCODING_LINEAR;
+ prinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ prinfo->encoding = AUDIO_ENCODING_LINEAR;
+ prinfo->precision = 32;
+ break;
+ default:
+ LOG("Unsupported format");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+#endif
+ if (ioctl(fd, AUDIO_SETINFO, info) == -1) {
+ return CUBEB_ERROR;
+ }
+ if (ioctl(fd, AUDIO_GETINFO, info) == -1) {
+ return CUBEB_ERROR;
+ }
return CUBEB_OK;
}
-static char const *
-sunaudio_get_backend_id(cubeb *context)
+static int
+sun_stream_stop(cubeb_stream * s)
{
- return "sunaudio";
+ pthread_mutex_lock(&s->mutex);
+ if (s->running) {
+ s->running = false;
+ pthread_mutex_unlock(&s->mutex);
+ pthread_join(s->thread, NULL);
+ } else {
+ pthread_mutex_unlock(&s->mutex);
+ }
+ return CUBEB_OK;
}
static void
-sunaudio_destroy(cubeb *context)
+sun_stream_destroy(cubeb_stream * s)
{
- DPR("sunaudio_destroy()\n");
- free(context);
+ sun_stream_stop(s);
+ pthread_mutex_destroy(&s->mutex);
+ if (s->play.fd != -1) {
+ close(s->play.fd);
+ }
+ if (s->record.fd != -1) {
+ close(s->record.fd);
+ }
+ free(s->play.buf);
+ free(s->record.buf);
+ free(s);
}
-static int
-sunaudio_stream_init(cubeb *context,
- cubeb_stream **stream,
- char const *stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void *user_ptr)
+static void
+sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
{
- struct cubeb_stream *s;
- DPR("sunaudio_stream_init(%s)\n", stream_name);
- size_t size;
-
- s = malloc(sizeof(struct cubeb_stream));
- if (s == NULL)
- return CUBEB_ERROR;
- s->context = context;
-
- // If UTAUDIODEV is set, use it with Sun Audio interface
- char * sa_device_name = getenv("UTAUDIODEV");
- char * dsp_device_name = NULL;
- if (!sa_device_name) {
- dsp_device_name = getenv("AUDIODSP");
- if (!dsp_device_name) {
- dsp_device_name = DEFAULT_DSP_DEVICE;
- }
- sa_device_name = getenv("AUDIODEV");
- if (!sa_device_name) {
- sa_device_name = DEFAULT_AUDIO_DEVICE;
- }
+ float * in = buf;
+ int32_t * out = buf;
+ int32_t * tail = out + sample_count;
+
+ while (out < tail) {
+ float f = *(in++) * vol;
+ if (f < -1.0)
+ f = -1.0;
+ else if (f > 1.0)
+ f = 1.0;
+ *(out++) = f * (float)INT32_MAX;
}
+}
- s->using_oss = 0;
- // Try to use OSS if available
- if (dsp_device_name) {
- s->fd = open(dsp_device_name, O_WRONLY | O_NONBLOCK);
- if (s->fd >= 0) {
- s->using_oss = 1;
- }
- }
+static void
+sun_linear32_to_float(void * buf, unsigned sample_count)
+{
+ int32_t * in = buf;
+ float * out = buf;
+ float * tail = out + sample_count;
- // Try Sun Audio
- if (!s->using_oss) {
- s->fd = open(sa_device_name, O_WRONLY | O_NONBLOCK);
+ while (out < tail) {
+ *(out++) = (1.0 / 0x80000000) * *(in++);
}
+}
- if (s->fd < 0) {
- free(s);
- DPR("sunaudio_stream_init(), open() failed\n");
- return CUBEB_ERROR;
+static void
+sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
+{
+ unsigned i;
+ int32_t multiplier = vol * 0x8000;
+
+ for (i = 0; i < sample_count; ++i) {
+ buf[i] = (buf[i] * multiplier) >> 15;
}
+}
- if (s->using_oss) {
- if (ioctl(s->fd, SNDCTL_DSP_SPEED, &output_stream_params->rate) < 0) {
- DPR("ioctl SNDCTL_DSP_SPEED failed.\n");
- close(s->fd);
- free(s);
- return CUBEB_ERROR_INVALID_FORMAT;
+static void *
+sun_io_routine(void * arg)
+{
+ cubeb_stream * s = arg;
+ cubeb_state state = CUBEB_STATE_STARTED;
+ size_t to_read = 0;
+ long to_write = 0;
+ size_t write_ofs = 0;
+ size_t read_ofs = 0;
+ int drain = 0;
+
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
+ while (state != CUBEB_STATE_ERROR) {
+ pthread_mutex_lock(&s->mutex);
+ if (!s->running) {
+ pthread_mutex_unlock(&s->mutex);
+ state = CUBEB_STATE_STOPPED;
+ break;
}
-
- if (ioctl(s->fd, SNDCTL_DSP_CHANNELS, &output_stream_params->channels) < 0) {
- DPR("ioctl SNDCTL_DSP_CHANNELS failed.\n");
- close(s->fd);
- free(s);
- return CUBEB_ERROR_INVALID_FORMAT;
+ pthread_mutex_unlock(&s->mutex);
+ if (s->record.fd != -1 && s->record.floating) {
+ sun_linear32_to_float(s->record.buf,
+ s->record.info.record.channels * SUN_BUFFER_FRAMES);
}
+ to_write = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf,
+ SUN_BUFFER_FRAMES);
+ if (to_write == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ if (s->play.fd != -1) {
+ float vol;
+
+ pthread_mutex_lock(&s->mutex);
+ vol = s->volume;
+ pthread_mutex_unlock(&s->mutex);
- int format = AFMT_S16_NE;
- if (ioctl(s->fd, SNDCTL_DSP_SETFMT, &format) < 0) {
- DPR("ioctl SNDCTL_DSP_SETFMT failed.\n");
- close(s->fd);
- free(s);
- return CUBEB_ERROR_INVALID_FORMAT;
+ if (s->play.floating) {
+ sun_float_to_linear32(s->play.buf,
+ s->play.info.play.channels * to_write, vol);
+ } else {
+ sun_linear16_set_vol(s->play.buf, s->play.info.play.channels * to_write,
+ vol);
+ }
}
- } else {
- audio_info_t audio_info;
- AUDIO_INITINFO(&audio_info)
- audio_info.play.sample_rate = output_stream_params->rate;
- audio_info.play.channels = output_stream_params->channels;
- audio_info.play.encoding = AUDIO_ENCODING_LINEAR;
- audio_info.play.precision = 16;
- if (ioctl(s->fd, AUDIO_SETINFO, &audio_info) == -1) {
- DPR("ioctl AUDIO_SETINFO failed.\n");
- close(s->fd);
- free(s);
- return CUBEB_ERROR_INVALID_FORMAT;
+ if (to_write < SUN_BUFFER_FRAMES) {
+ drain = 1;
}
- }
-
- s->conv = 0;
- switch (output_stream_params->format) {
- case CUBEB_SAMPLE_S16NE:
- s->bytes_per_ch = 2;
- break;
- case CUBEB_SAMPLE_FLOAT32NE:
- s->bytes_per_ch = 4;
- s->conv = 1;
+ to_write = s->play.fd != -1 ? to_write : 0;
+ to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
+ write_ofs = 0;
+ read_ofs = 0;
+ while (to_write > 0 || to_read > 0) {
+ size_t bytes;
+ ssize_t n, frames;
+
+ if (to_write > 0) {
+ bytes = to_write * s->play.frame_size;
+ if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) <
+ 0) {
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ frames = n / s->play.frame_size;
+ pthread_mutex_lock(&s->mutex);
+ s->frames_written += frames;
+ pthread_mutex_unlock(&s->mutex);
+ to_write -= frames;
+ write_ofs += n;
+ }
+ if (to_read > 0) {
+ bytes = to_read * s->record.frame_size;
+ if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs,
+ bytes)) < 0) {
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ frames = n / s->record.frame_size;
+ to_read -= frames;
+ read_ofs += n;
+ }
+ }
+ if (drain && state != CUBEB_STATE_ERROR) {
+ state = CUBEB_STATE_DRAINED;
break;
- default:
- DPR("sunaudio_stream_init() unsupported format\n");
- close(s->fd);
- free(s);
- return CUBEB_ERROR_INVALID_FORMAT;
+ }
}
+ s->state_cb(s, s->user_ptr, state);
+ return NULL;
+}
- s->active = 0;
- s->rate = output_stream_params->rate;
- s->n_channles = output_stream_params->channels;
- s->data_cb = data_callback;
+static int
+sun_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int ret = CUBEB_OK;
+ cubeb_stream * s = NULL;
+
+ (void)stream_name;
+ (void)latency_frames;
+ if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ s->record.fd = -1;
+ s->play.fd = -1;
+ if (input_device != 0) {
+ snprintf(s->record.name, sizeof(s->record.name), "/dev/audio%zu",
+ (uintptr_t)input_device - 1);
+ } else {
+ snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
+ }
+ if (output_device != 0) {
+ snprintf(s->play.name, sizeof(s->play.name), "/dev/audio%zu",
+ (uintptr_t)output_device - 1);
+ } else {
+ snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
+ }
+ if (input_stream_params != NULL) {
+ if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ if (s->record.fd == -1) {
+ if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
+ LOG("Audio device could not be opened as read-only");
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ }
+ AUDIO_INITINFO(&s->record.info);
+#ifdef AUMODE_RECORD
+ s->record.info.mode = AUMODE_RECORD;
+#endif
+ if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
+ &s->record.info, &s->record.info.record)) !=
+ CUBEB_OK) {
+ LOG("Setting record params failed");
+ goto error;
+ }
+ s->record.floating =
+ (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ }
+ if (output_stream_params != NULL) {
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ if (s->play.fd == -1) {
+ if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
+ LOG("Audio device could not be opened as write-only");
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ }
+ AUDIO_INITINFO(&s->play.info);
+#ifdef AUMODE_PLAY
+ s->play.info.mode = AUMODE_PLAY;
+#endif
+ if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
+ &s->play.info, &s->play.info.play)) !=
+ CUBEB_OK) {
+ LOG("Setting play params failed");
+ goto error;
+ }
+ s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ }
+ s->context = context;
+ s->volume = 1.0;
s->state_cb = state_callback;
- s->arg = user_ptr;
+ s->data_cb = data_callback;
+ s->user_ptr = user_ptr;
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
- free(s);
- return CUBEB_ERROR;
+ LOG("Failed to create mutex");
+ goto error;
}
- s->frm_played = 0;
- s->n_frm = s->rate * BUF_SIZE_MS / 1000;
- s->buffer_size = s->bytes_per_ch * s->n_channles * s->n_frm;
- s->buf = malloc(s->buffer_size);
- if (s->buf == NULL) {
- close(s->fd);
- free(s);
- return CUBEB_ERROR;
+ s->play.frame_size =
+ s->play.info.play.channels * (s->play.info.play.precision / 8);
+ if (s->play.fd != -1 &&
+ (s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ s->record.frame_size =
+ s->record.info.record.channels * (s->record.info.record.precision / 8);
+ if (s->record.fd != -1 &&
+ (s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) ==
+ NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
}
-
*stream = s;
- DPR("sunaudio_stream_init() end, ok\n");
return CUBEB_OK;
-}
-
-static void
-sunaudio_stream_destroy(cubeb_stream *s)
-{
- DPR("sunaudio_stream_destroy()\n");
- if (s->fd > 0) {
- // Flush buffer
- if (s->using_oss) {
- ioctl(s->fd, SNDCTL_DSP_HALT_OUTPUT);
- } else {
- ioctl(s->fd, I_FLUSH);
- }
- close(s->fd);
+error:
+ if (s != NULL) {
+ sun_stream_destroy(s);
}
- free(s->buf);
- free(s);
+ return ret;
}
static int
-sunaudio_stream_start(cubeb_stream *s)
+sun_stream_start(cubeb_stream * s)
{
- int err;
-
- DPR("sunaudio_stream_start()\n");
- s->active = 1;
- err = pthread_create(&s->th, NULL, sunaudio_mainloop, s);
- if (err) {
- s->active = 0;
+ s->running = true;
+ if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
+ LOG("Couldn't create thread");
return CUBEB_ERROR;
}
return CUBEB_OK;
}
static int
-sunaudio_stream_stop(cubeb_stream *s)
+sun_stream_get_position(cubeb_stream * s, uint64_t * position)
{
- void *dummy;
+#ifdef AUDIO_GETOOFFS
+ struct audio_offset offset;
- DPR("sunaudio_stream_stop()\n");
- if (s->active) {
- s->active = 0;
- pthread_join(s->th, &dummy);
+ if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
+ return CUBEB_ERROR;
}
+ s->blocks_written += offset.deltablks;
+ *position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
return CUBEB_OK;
-}
-
-static int
-sunaudio_stream_get_position(cubeb_stream *s, uint64_t *p)
-{
- int rv = CUBEB_OK;
+#else
pthread_mutex_lock(&s->mutex);
- if (s->active && s->fd > 0) {
- if (s->using_oss) {
- int delay;
- ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay);
- int64_t t = s->frm_played - delay / s->n_channles / 2;
- if (t < 0) {
- *p = 0;
- } else {
- *p = t;
- }
- } else {
- audio_info_t info;
- ioctl(s->fd, AUDIO_GETINFO, &info);
- *p = info.play.samples;
- }
- DPR("sunaudio_stream_get_position() %lld\n", *p);
- } else {
- rv = CUBEB_ERROR;
- }
+ *position = s->frames_written;
pthread_mutex_unlock(&s->mutex);
- return rv;
+ return CUBEB_OK;
+#endif
}
static int
-sunaudio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
{
- if (!ctx || !max_channels)
- return CUBEB_ERROR;
+#ifdef AUDIO_GETBUFINFO
+ struct audio_info info;
- *max_channels = 2;
+ if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
+ return CUBEB_ERROR;
+ }
+ *latency = (info.play.seek + info.blocksize) / s->play.frame_size;
return CUBEB_OK;
+#else
+ cubeb_stream_params params;
+
+ params.rate = s->play.info.play.sample_rate;
+
+ return sun_get_min_latency(NULL, params, latency);
+#endif
}
static int
-sunaudio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+sun_stream_set_volume(cubeb_stream * stream, float volume)
{
- if (!ctx || !rate)
- return CUBEB_ERROR;
-
- // XXX Not yet implemented.
- *rate = 44100;
-
+ pthread_mutex_lock(&stream->mutex);
+ stream->volume = volume;
+ pthread_mutex_unlock(&stream->mutex);
return CUBEB_OK;
}
static int
-sunaudio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
+sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
{
- if (!ctx || !latency_ms)
+ *device = calloc(1, sizeof(cubeb_device));
+ if (*device == NULL) {
return CUBEB_ERROR;
-
- // XXX Not yet implemented.
- *latency_ms = 20;
-
+ }
+ (*device)->input_name =
+ stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
+ (*device)->output_name =
+ stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
return CUBEB_OK;
}
static int
-sunaudio_stream_get_latency(cubeb_stream * s, uint32_t * latency)
+sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
{
- if (!s || !latency)
- return CUBEB_ERROR;
-
- int rv = CUBEB_OK;
- pthread_mutex_lock(&s->mutex);
- if (s->active && s->fd > 0) {
- if (s->using_oss) {
- int delay;
- ioctl(s->fd, SNDCTL_DSP_GETODELAY, &delay);
- *latency = delay / s->n_channles / 2 / s->rate;
- } else {
- audio_info_t info;
- ioctl(s->fd, AUDIO_GETINFO, &info);
- *latency = (s->frm_played - info.play.samples) / s->rate;
- }
- DPR("sunaudio_stream_get_position() %lld\n", *p);
- } else {
- rv = CUBEB_ERROR;
- }
- pthread_mutex_unlock(&s->mutex);
- return rv;
+ (void)stream;
+ free(device->input_name);
+ free(device->output_name);
+ free(device);
+ return CUBEB_OK;
}
-static struct cubeb_ops const sunaudio_ops = {
- .init = sunaudio_init,
- .get_backend_id = sunaudio_get_backend_id,
- .destroy = sunaudio_destroy,
- .get_preferred_sample_rate = sunaudio_get_preferred_sample_rate,
- .stream_init = sunaudio_stream_init,
- .stream_destroy = sunaudio_stream_destroy,
- .stream_start = sunaudio_stream_start,
- .stream_stop = sunaudio_stream_stop,
- .stream_get_position = sunaudio_stream_get_position,
- .get_max_channel_count = sunaudio_get_max_channel_count,
- .get_min_latency = sunaudio_get_min_latency,
- .stream_get_latency = sunaudio_stream_get_latency
-};
+static struct cubeb_ops const sun_ops = {
+ .init = sun_init,
+ .get_backend_id = sun_get_backend_id,
+ .get_max_channel_count = sun_get_max_channel_count,
+ .get_min_latency = sun_get_min_latency,
+ .get_preferred_sample_rate = sun_get_preferred_sample_rate,
+ .enumerate_devices = sun_enumerate_devices,
+ .device_collection_destroy = sun_device_collection_destroy,
+ .destroy = sun_destroy,
+ .stream_init = sun_stream_init,
+ .stream_destroy = sun_stream_destroy,
+ .stream_start = sun_stream_start,
+ .stream_stop = sun_stream_stop,
+ .stream_get_position = sun_stream_get_position,
+ .stream_get_latency = sun_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = sun_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = sun_get_current_device,
+ .stream_device_destroy = sun_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/media/libcubeb/src/cubeb_utils.cpp b/media/libcubeb/src/cubeb_utils.cpp
new file mode 100644
index 0000000000..dd1aef7b82
--- /dev/null
+++ b/media/libcubeb/src/cubeb_utils.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright © 2018 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "cubeb_utils.h"
+
+size_t
+cubeb_sample_size(cubeb_sample_format format)
+{
+ switch (format) {
+ case CUBEB_SAMPLE_S16LE:
+ case CUBEB_SAMPLE_S16BE:
+ return sizeof(int16_t);
+ case CUBEB_SAMPLE_FLOAT32LE:
+ case CUBEB_SAMPLE_FLOAT32BE:
+ return sizeof(float);
+ default:
+ // should never happen as all cases are handled above.
+ assert(false);
+ return 0;
+ }
+}
diff --git a/media/libcubeb/src/cubeb_utils.h b/media/libcubeb/src/cubeb_utils.h
index d8e9928fe2..fd7a3d7406 100644
--- a/media/libcubeb/src/cubeb_utils.h
+++ b/media/libcubeb/src/cubeb_utils.h
@@ -8,97 +8,154 @@
#if !defined(CUBEB_UTILS)
#define CUBEB_UTILS
+#include "cubeb/cubeb.h"
+
+#ifdef __cplusplus
+
+#include <assert.h>
+#include <mutex>
#include <stdint.h>
#include <string.h>
-#include <assert.h>
#include <type_traits>
-#if defined(WIN32)
+#if defined(_WIN32)
#include "cubeb_utils_win.h"
#else
#include "cubeb_utils_unix.h"
#endif
/** Similar to memcpy, but accounts for the size of an element. */
-template<typename T>
-void PodCopy(T * destination, const T * source, size_t count)
+template <typename T>
+void
+PodCopy(T * destination, const T * source, size_t count)
{
static_assert(std::is_trivial<T>::value, "Requires trivial type");
+ assert(destination && source);
memcpy(destination, source, count * sizeof(T));
}
/** Similar to memmove, but accounts for the size of an element. */
-template<typename T>
-void PodMove(T * destination, const T * source, size_t count)
+template <typename T>
+void
+PodMove(T * destination, const T * source, size_t count)
{
static_assert(std::is_trivial<T>::value, "Requires trivial type");
+ assert(destination && source);
memmove(destination, source, count * sizeof(T));
}
/** Similar to a memset to zero, but accounts for the size of an element. */
-template<typename T>
-void PodZero(T * destination, size_t count)
+template <typename T>
+void
+PodZero(T * destination, size_t count)
{
static_assert(std::is_trivial<T>::value, "Requires trivial type");
- memset(destination, 0, count * sizeof(T));
+ assert(destination);
+ memset(destination, 0, count * sizeof(T));
+}
+
+namespace {
+template <typename T, typename Trait>
+void
+Copy(T * destination, const T * source, size_t count, Trait)
+{
+ for (size_t i = 0; i < count; i++) {
+ destination[i] = source[i];
+ }
+}
+
+template <typename T>
+void
+Copy(T * destination, const T * source, size_t count, std::true_type)
+{
+ PodCopy(destination, source, count);
+}
+} // namespace
+
+/**
+ * This allows copying a number of elements from a `source` pointer to a
+ * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that
+ * calls the constructors and destructors otherwise.
+ */
+template <typename T>
+void
+Copy(T * destination, const T * source, size_t count)
+{
+ assert(destination && source);
+ Copy(destination, source, count, typename std::is_trivial<T>::type());
}
-template<typename T>
-class auto_array
+namespace {
+template <typename T, typename Trait>
+void
+ConstructDefault(T * destination, size_t count, Trait)
{
+ for (size_t i = 0; i < count; i++) {
+ destination[i] = T();
+ }
+}
+
+template <typename T>
+void
+ConstructDefault(T * destination, size_t count, std::true_type)
+{
+ PodZero(destination, count);
+}
+} // namespace
+
+/**
+ * This allows zeroing (using memset) or default-constructing a number of
+ * elements calling the constructors and destructors if necessary.
+ */
+template <typename T>
+void
+ConstructDefault(T * destination, size_t count)
+{
+ assert(destination);
+ ConstructDefault(destination, count, typename std::is_arithmetic<T>::type());
+}
+
+template <typename T> class auto_array {
public:
explicit auto_array(uint32_t capacity = 0)
- : data_(capacity ? new T[capacity] : nullptr)
- , capacity_(capacity)
- , length_(0)
- {}
-
- ~auto_array()
+ : data_(capacity ? new T[capacity] : nullptr), capacity_(capacity),
+ length_(0)
{
- delete [] data_;
}
+ ~auto_array() { delete[] data_; }
+
/** Get a constant pointer to the underlying data. */
- T * data() const
- {
- return data_;
- }
+ T * data() const { return data_; }
+
+ T * end() const { return data_ + length_; }
- const T& at(size_t index) const
+ const T & at(size_t index) const
{
assert(index < length_ && "out of range");
return data_[index];
}
- T& at(size_t index)
+ T & at(size_t index)
{
assert(index < length_ && "out of range");
return data_[index];
}
/** Get how much underlying storage this auto_array has. */
- size_t capacity() const
- {
- return capacity_;
- }
+ size_t capacity() const { return capacity_; }
/** Get how much elements this auto_array contains. */
- size_t length() const
- {
- return length_;
- }
+ size_t length() const { return length_; }
/** Keeps the storage, but removes all the elements from the array. */
- void clear()
- {
- length_ = 0;
- }
+ void clear() { length_ = 0; }
- /** Change the storage of this auto array, copying the elements to the new
- * storage.
- * @returns true in case of success
- * @returns false if the new capacity is not big enough to accomodate for the
- * elements in the array.
- */
+ /** Change the storage of this auto array, copying the elements to the new
+ * storage.
+ * @returns true in case of success
+ * @returns false if the new capacity is not big enough to accomodate for the
+ * elements in the array.
+ */
bool reserve(size_t new_capacity)
{
if (new_capacity < length_) {
@@ -109,17 +166,17 @@ public:
PodCopy(new_data, data_, length_);
}
capacity_ = new_capacity;
- delete [] data_;
+ delete[] data_;
data_ = new_data;
return true;
}
- /** Append `length` elements to the end of the array, resizing the array if
- * needed.
- * @parameter elements the elements to append to the array.
- * @parameter length the number of elements to append to the array.
- */
+ /** Append `length` elements to the end of the array, resizing the array if
+ * needed.
+ * @parameter elements the elements to append to the array.
+ * @parameter length the number of elements to append to the array.
+ */
void push(const T * elements, size_t length)
{
if (length_ + length > capacity_) {
@@ -157,17 +214,14 @@ public:
}
/** Return the number of free elements in the array. */
- size_t available() const
- {
- return capacity_ - length_;
- }
+ size_t available() const { return capacity_ - length_; }
/** Copies `length` elements to `elements` if it is not null, and shift
- * the remaining elements of the `auto_array` to the beginning.
- * @parameter elements a buffer to copy the elements to, or nullptr.
- * @parameter length the number of elements to copy.
- * @returns true in case of success.
- * @returns false if the auto_array contains less than `length` elements. */
+ * the remaining elements of the `auto_array` to the beginning.
+ * @parameter elements a buffer to copy the elements to, or nullptr.
+ * @parameter length the number of elements to copy.
+ * @returns true in case of success.
+ * @returns false if the auto_array contains less than `length` elements. */
bool pop(T * elements, size_t length)
{
if (length > length_) {
@@ -198,18 +252,58 @@ private:
size_t length_;
};
-struct auto_lock {
- explicit auto_lock(owned_critical_section & lock)
- : lock(lock)
- {
- lock.enter();
- }
- ~auto_lock()
+struct auto_array_wrapper {
+ virtual void push(void * elements, size_t length) = 0;
+ virtual size_t length() = 0;
+ virtual void push_silence(size_t length) = 0;
+ virtual bool pop(size_t length) = 0;
+ virtual void * data() = 0;
+ virtual void * end() = 0;
+ virtual void clear() = 0;
+ virtual bool reserve(size_t capacity) = 0;
+ virtual void set_length(size_t length) = 0;
+ virtual ~auto_array_wrapper() {}
+};
+
+template <typename T>
+struct auto_array_wrapper_impl : public auto_array_wrapper {
+ auto_array_wrapper_impl() {}
+
+ explicit auto_array_wrapper_impl(uint32_t size) : ar(size) {}
+
+ void push(void * elements, size_t length) override
{
- lock.leave();
+ ar.push(static_cast<T *>(elements), length);
}
+
+ size_t length() override { return ar.length(); }
+
+ void push_silence(size_t length) override { ar.push_silence(length); }
+
+ bool pop(size_t length) override { return ar.pop(nullptr, length); }
+
+ void * data() override { return ar.data(); }
+
+ void * end() override { return ar.end(); }
+
+ void clear() override { ar.clear(); }
+
+ bool reserve(size_t capacity) override { return ar.reserve(capacity); }
+
+ void set_length(size_t length) override { ar.set_length(length); }
+
+ ~auto_array_wrapper_impl() { ar.clear(); }
+
private:
- owned_critical_section & lock;
+ auto_array<T> ar;
};
+extern "C" {
+size_t
+cubeb_sample_size(cubeb_sample_format format);
+}
+
+using auto_lock = std::lock_guard<owned_critical_section>;
+#endif // __cplusplus
+
#endif /* CUBEB_UTILS */
diff --git a/media/libcubeb/src/cubeb_utils_unix.h b/media/libcubeb/src/cubeb_utils_unix.h
index 80219d58b1..b6618ca45e 100644
--- a/media/libcubeb/src/cubeb_utils_unix.h
+++ b/media/libcubeb/src/cubeb_utils_unix.h
@@ -8,13 +8,12 @@
#if !defined(CUBEB_UTILS_UNIX)
#define CUBEB_UTILS_UNIX
-#include <pthread.h>
#include <errno.h>
+#include <pthread.h>
#include <stdio.h>
/* This wraps a critical section to track the owner in debug mode. */
-class owned_critical_section
-{
+class owned_critical_section {
public:
owned_critical_section()
{
@@ -29,7 +28,7 @@ public:
#ifndef NDEBUG
int r =
#endif
- pthread_mutex_init(&mutex, &attr);
+ pthread_mutex_init(&mutex, &attr);
#ifndef NDEBUG
assert(r == 0);
#endif
@@ -42,29 +41,29 @@ public:
#ifndef NDEBUG
int r =
#endif
- pthread_mutex_destroy(&mutex);
+ pthread_mutex_destroy(&mutex);
#ifndef NDEBUG
assert(r == 0);
#endif
}
- void enter()
+ void lock()
{
#ifndef NDEBUG
int r =
#endif
- pthread_mutex_lock(&mutex);
+ pthread_mutex_lock(&mutex);
#ifndef NDEBUG
assert(r == 0 && "Deadlock");
#endif
}
- void leave()
+ void unlock()
{
#ifndef NDEBUG
int r =
#endif
- pthread_mutex_unlock(&mutex);
+ pthread_mutex_unlock(&mutex);
#ifndef NDEBUG
assert(r == 0 && "Unlocking unlocked mutex");
#endif
@@ -82,8 +81,8 @@ private:
pthread_mutex_t mutex;
// Disallow copy and assignment because pthread_mutex_t cannot be copied.
- owned_critical_section(const owned_critical_section&);
- owned_critical_section& operator=(const owned_critical_section&);
+ owned_critical_section(const owned_critical_section &);
+ owned_critical_section & operator=(const owned_critical_section &);
};
#endif /* CUBEB_UTILS_UNIX */
diff --git a/media/libcubeb/src/cubeb_utils_win.h b/media/libcubeb/src/cubeb_utils_win.h
index 2b094cd93d..4c47f454c7 100644
--- a/media/libcubeb/src/cubeb_utils_win.h
+++ b/media/libcubeb/src/cubeb_utils_win.h
@@ -8,28 +8,25 @@
#if !defined(CUBEB_UTILS_WIN)
#define CUBEB_UTILS_WIN
-#include <windows.h>
#include "cubeb-internal.h"
+#include <windows.h>
/* This wraps a critical section to track the owner in debug mode, adapted from
- NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx */
-class owned_critical_section
-{
+ NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx
+ */
+class owned_critical_section {
public:
owned_critical_section()
#ifndef NDEBUG
- : owner(0)
+ : owner(0)
#endif
{
InitializeCriticalSection(&critical_section);
}
- ~owned_critical_section()
- {
- DeleteCriticalSection(&critical_section);
- }
+ ~owned_critical_section() { DeleteCriticalSection(&critical_section); }
- void enter()
+ void lock()
{
EnterCriticalSection(&critical_section);
#ifndef NDEBUG
@@ -38,7 +35,7 @@ public:
#endif
}
- void leave()
+ void unlock()
{
#ifndef NDEBUG
/* GetCurrentThreadId cannot return 0: it is not a the valid thread id */
@@ -64,8 +61,8 @@ private:
#endif
// Disallow copy and assignment because CRICICAL_SECTION cannot be copied.
- owned_critical_section(const owned_critical_section&);
- owned_critical_section& operator=(const owned_critical_section&);
+ owned_critical_section(const owned_critical_section &);
+ owned_critical_section & operator=(const owned_critical_section &);
};
#endif /* CUBEB_UTILS_WIN */
diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp
index e88d6becd8..0c794dcbd2 100644
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -4,121 +4,232 @@
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
+#define _WIN32_WINNT 0x0603
#define NOMINMAX
-#include <initguid.h>
-#include <windows.h>
-#include <mmdeviceapi.h>
-#include <windef.h>
+#include <algorithm>
+#include <atomic>
#include <audioclient.h>
+#include <avrt.h>
+#include <cmath>
#include <devicetopology.h>
+#include <initguid.h>
+#include <limits>
+#include <memory>
+#include <mmdeviceapi.h>
#include <process.h>
-#include <avrt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
-#include <stdint.h>
-#include <cmath>
-#include <algorithm>
-#include <memory>
-#include <limits>
-#include <atomic>
+#include <vector>
+#include <windef.h>
+#include <windows.h>
-#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
#include "cubeb_resampler.h"
+#include "cubeb_strings.h"
#include "cubeb_utils.h"
-/* devicetopology.h missing in MinGW. */
-#ifndef __devicetopology_h__
-#include "cubeb_devicetopology.h"
+// Windows 10 exposes the IAudioClient3 interface to create low-latency streams.
+// Copy the interface definition from audioclient.h here to make the code
+// simpler and so that we can still access IAudioClient3 via COM if cubeb was
+// compiled against an older SDK.
+#ifndef __IAudioClient3_INTERFACE_DEFINED__
+#define __IAudioClient3_INTERFACE_DEFINED__
+MIDL_INTERFACE("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42")
+IAudioClient3 : public IAudioClient
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
+ /* [annotation][in] */
+ _In_ const WAVEFORMATEX * pFormat,
+ /* [annotation][out] */
+ _Out_ UINT32 * pDefaultPeriodInFrames,
+ /* [annotation][out] */
+ _Out_ UINT32 * pFundamentalPeriodInFrames,
+ /* [annotation][out] */
+ _Out_ UINT32 * pMinPeriodInFrames,
+ /* [annotation][out] */
+ _Out_ UINT32 * pMaxPeriodInFrames) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
+ /* [unique][annotation][out] */
+ _Out_ WAVEFORMATEX * *ppFormat,
+ /* [annotation][out] */
+ _Out_ UINT32 * pCurrentPeriodInFrames) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
+ /* [annotation][in] */
+ _In_ DWORD StreamFlags,
+ /* [annotation][in] */
+ _In_ UINT32 PeriodInFrames,
+ /* [annotation][in] */
+ _In_ const WAVEFORMATEX * pFormat,
+ /* [annotation][in] */
+ _In_opt_ LPCGUID AudioSessionGuid) = 0;
+};
+#ifdef __CRT_UUID_DECL
+// Required for MinGW
+__CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B,
+ 0x7A, 0x59, 0x87, 0xAD, 0x42)
#endif
-
-/* Taken from winbase.h, Not in MinGW. */
-#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
-#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only
+#endif
+// Copied from audioclient.h in the Windows 10 SDK
+#ifndef AUDCLNT_E_ENGINE_PERIODICITY_LOCKED
+#define AUDCLNT_E_ENGINE_PERIODICITY_LOCKED AUDCLNT_ERR(0x028)
#endif
#ifndef PKEY_Device_FriendlyName
-DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
+DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,
+ 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0,
+ 14); // DEVPROP_TYPE_STRING
#endif
#ifndef PKEY_Device_InstanceId
-DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 0x00000100); // VT_LPWSTR
+DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e,
+ 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57,
+ 0x00000100); // VT_LPWSTR
#endif
namespace {
-template<typename T, size_t N>
-constexpr size_t
-ARRAY_LENGTH(T(&)[N])
+
+const int64_t LATENCY_NOT_AVAILABLE_YET = -1;
+
+struct com_heap_ptr_deleter {
+ void operator()(void * ptr) const noexcept { CoTaskMemFree(ptr); }
+};
+
+template <typename T>
+using com_heap_ptr = std::unique_ptr<T, com_heap_ptr_deleter>;
+
+template <typename T, size_t N> constexpr size_t ARRAY_LENGTH(T (&)[N])
{
return N;
}
-void
-SafeRelease(HANDLE handle)
-{
- if (handle) {
- CloseHandle(handle);
+template <typename T> class no_addref_release : public T {
+ ULONG STDMETHODCALLTYPE AddRef() = 0;
+ ULONG STDMETHODCALLTYPE Release() = 0;
+};
+
+template <typename T> class com_ptr {
+public:
+ com_ptr() noexcept = default;
+
+ com_ptr(com_ptr const & other) noexcept = delete;
+ com_ptr & operator=(com_ptr const & other) noexcept = delete;
+ T ** operator&() const noexcept = delete;
+
+ ~com_ptr() noexcept { release(); }
+
+ com_ptr(com_ptr && other) noexcept : ptr(other.ptr) { other.ptr = nullptr; }
+
+ com_ptr & operator=(com_ptr && other) noexcept
+ {
+ if (ptr != other.ptr) {
+ release();
+ ptr = other.ptr;
+ other.ptr = nullptr;
+ }
+ return *this;
}
-}
-template <typename T>
-void SafeRelease(T * ptr)
-{
- if (ptr) {
- ptr->Release();
+ explicit operator bool() const noexcept { return nullptr != ptr; }
+
+ no_addref_release<T> * operator->() const noexcept
+ {
+ return static_cast<no_addref_release<T> *>(ptr);
}
-}
-struct auto_com {
- auto_com() {
- result = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ T * get() const noexcept { return ptr; }
+
+ T ** receive() noexcept
+ {
+ XASSERT(ptr == nullptr);
+ return &ptr;
}
- ~auto_com() {
- if (result == RPC_E_CHANGED_MODE) {
- // This is not an error, COM was not initialized by this function, so it is
- // not necessary to uninit it.
- LOG("COM was already initialized in STA.");
- } else if (result == S_FALSE) {
- // This is not an error. We are allowed to call CoInitializeEx more than
- // once, as long as it is matches by an CoUninitialize call.
- // We do that in the dtor which is guaranteed to be called.
- LOG("COM was already initialized in MTA");
- }
- if (SUCCEEDED(result)) {
- CoUninitialize();
- }
+
+ void ** receive_vpp() noexcept
+ {
+ return reinterpret_cast<void **>(receive());
}
- bool ok() {
- return result == RPC_E_CHANGED_MODE || SUCCEEDED(result);
+
+ com_ptr & operator=(std::nullptr_t) noexcept
+ {
+ release();
+ return *this;
}
-private:
- HRESULT result;
-};
-typedef HANDLE (WINAPI *set_mm_thread_characteristics_function)(
- const char * TaskName, LPDWORD TaskIndex);
-typedef BOOL (WINAPI *revert_mm_thread_characteristics_function)(HANDLE handle);
+ void reset(T * p = nullptr) noexcept
+ {
+ release();
+ ptr = p;
+ }
-extern cubeb_ops const wasapi_ops;
+private:
+ void release() noexcept
+ {
+ T * temp = ptr;
-int wasapi_stream_stop(cubeb_stream * stm);
-int wasapi_stream_start(cubeb_stream * stm);
-void close_wasapi_stream(cubeb_stream * stm);
-int setup_wasapi_stream(cubeb_stream * stm);
-static char * wstr_to_utf8(const wchar_t * str);
-static std::unique_ptr<const wchar_t[]> utf8_to_wstr(char* str);
+ if (temp) {
+ ptr = nullptr;
+ temp->Release();
+ }
+ }
-}
+ T * ptr = nullptr;
+};
-struct cubeb
-{
- cubeb_ops const * ops;
- /* Library dynamically opened to increase the render thread priority, and
- the two function pointers we need. */
- HMODULE mmcss_module;
- set_mm_thread_characteristics_function set_mm_thread_characteristics;
- revert_mm_thread_characteristics_function revert_mm_thread_characteristics;
+extern cubeb_ops const wasapi_ops;
+
+int
+wasapi_stream_stop(cubeb_stream * stm);
+int
+wasapi_stream_start(cubeb_stream * stm);
+void
+close_wasapi_stream(cubeb_stream * stm);
+int
+setup_wasapi_stream(cubeb_stream * stm);
+ERole
+pref_to_role(cubeb_stream_prefs param);
+int
+wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
+ IMMDeviceEnumerator * enumerator, IMMDevice * dev);
+void
+wasapi_destroy_device(cubeb_device_info * device_info);
+static int
+wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * out);
+static int
+wasapi_device_collection_destroy(cubeb * ctx,
+ cubeb_device_collection * collection);
+static char const *
+wstr_to_utf8(wchar_t const * str);
+static std::unique_ptr<wchar_t const[]>
+utf8_to_wstr(char const * str);
+
+} // namespace
+
+class wasapi_collection_notification_client;
+class monitor_device_notifications;
+
+struct cubeb {
+ cubeb_ops const * ops = &wasapi_ops;
+ cubeb_strings * device_ids;
+ /* Device enumerator to get notifications when the
+ device collection change. */
+ com_ptr<IMMDeviceEnumerator> device_collection_enumerator;
+ com_ptr<wasapi_collection_notification_client> collection_notification_client;
+ /* Collection changed for input (capture) devices. */
+ cubeb_device_collection_changed_callback input_collection_changed_callback =
+ nullptr;
+ void * input_collection_changed_user_ptr = nullptr;
+ /* Collection changed for output (render) devices. */
+ cubeb_device_collection_changed_callback output_collection_changed_callback =
+ nullptr;
+ void * output_collection_changed_user_ptr = nullptr;
+ UINT64 performance_counter_frequency;
};
class wasapi_endpoint_notification_client;
@@ -132,27 +243,49 @@ class wasapi_endpoint_notification_client;
*/
typedef bool (*wasapi_refill_callback)(cubeb_stream * stm);
-struct cubeb_stream
-{
- cubeb * context;
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context = nullptr;
+ void * user_ptr = nullptr;
+ /**/
+
/* Mixer pameters. We need to convert the input stream to this
samplerate/channel layout, as WASAPI does not resample nor upmix
itself. */
- cubeb_stream_params input_mix_params;
- cubeb_stream_params output_mix_params;
+ cubeb_stream_params input_mix_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ cubeb_stream_params output_mix_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
/* Stream parameters. This is what the client requested,
* and what will be presented in the callback. */
- cubeb_stream_params input_stream_params;
- cubeb_stream_params output_stream_params;
+ cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ /* A MMDevice role for this stream: either communication or console here. */
+ ERole role;
+ /* True if this stream will transport voice-data. */
+ bool voice;
+ /* True if the input device of this stream is using bluetooth handsfree. */
+ bool input_bluetooth_handsfree;
/* The input and output device, or NULL for default. */
- cubeb_devid input_device;
- cubeb_devid output_device;
+ std::unique_ptr<const wchar_t[]> input_device_id;
+ std::unique_ptr<const wchar_t[]> output_device_id;
+ com_ptr<IMMDevice> input_device;
+ com_ptr<IMMDevice> output_device;
/* The latency initially requested for this stream, in frames. */
- unsigned latency;
- cubeb_state_callback state_callback;
- cubeb_data_callback data_callback;
- wasapi_refill_callback refill_callback;
- void * user_ptr;
+ unsigned latency = 0;
+ cubeb_state_callback state_callback = nullptr;
+ cubeb_data_callback data_callback = nullptr;
+ wasapi_refill_callback refill_callback = nullptr;
+ /* True when a loopback device is requested with no output device. In this
+ case a dummy output device is opened to drive the loopback, but should not
+ be exposed. */
+ bool has_dummy_output = false;
/* Lifetime considerations:
- client, render_client, audio_clock and audio_stream_volume are interface
pointer to the IAudioClient.
@@ -160,84 +293,338 @@ struct cubeb_stream
mix_buffer are the same as the cubeb_stream instance. */
/* Main handle on the WASAPI stream. */
- IAudioClient * output_client;
+ com_ptr<IAudioClient> output_client;
/* Interface pointer to use the event-driven interface. */
- IAudioRenderClient * render_client;
+ com_ptr<IAudioRenderClient> render_client;
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
/* Interface pointer to use the volume facilities. */
- IAudioStreamVolume * audio_stream_volume;
+ com_ptr<IAudioStreamVolume> audio_stream_volume;
+#endif
/* Interface pointer to use the stream audio clock. */
- IAudioClock * audio_clock;
+ com_ptr<IAudioClock> audio_clock;
/* Frames written to the stream since it was opened. Reset on device
change. Uses mix_params.rate. */
- UINT64 frames_written;
+ UINT64 frames_written = 0;
/* Frames written to the (logical) stream since it was first
created. Updated on device change. Uses stream_params.rate. */
- UINT64 total_frames_written;
+ UINT64 total_frames_written = 0;
/* Last valid reported stream position. Used to ensure the position
reported by stream_get_position increases monotonically. */
- UINT64 prev_position;
+ UINT64 prev_position = 0;
/* Device enumerator to be able to be notified when the default
device change. */
- IMMDeviceEnumerator * device_enumerator;
+ com_ptr<IMMDeviceEnumerator> device_enumerator;
/* Device notification client, to be able to be notified when the default
audio device changes and route the audio to the new default audio output
device */
- wasapi_endpoint_notification_client * notification_client;
+ com_ptr<wasapi_endpoint_notification_client> notification_client;
/* Main andle to the WASAPI capture stream. */
- IAudioClient * input_client;
+ com_ptr<IAudioClient> input_client;
/* Interface to use the event driven capture interface */
- IAudioCaptureClient * capture_client;
+ com_ptr<IAudioCaptureClient> capture_client;
/* This event is set by the stream_stop and stream_destroy
function, so the render loop can exit properly. */
- HANDLE shutdown_event;
+ HANDLE shutdown_event = 0;
/* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
The reconfiguration is handled by the render loop thread. */
- HANDLE reconfigure_event;
+ HANDLE reconfigure_event = 0;
/* This is set by WASAPI when we should refill the stream. */
- HANDLE refill_event;
+ HANDLE refill_event = 0;
/* This is set by WASAPI when we should read from the input stream. In
* practice, we read from the input stream in the output callback, so
* this is not used, but it is necessary to start getting input data. */
- HANDLE input_available_event;
+ HANDLE input_available_event = 0;
/* Each cubeb_stream has its own thread. */
- HANDLE thread;
+ HANDLE thread = 0;
/* The lock protects all members that are touched by the render thread or
change during a device reset, including: audio_clock, audio_stream_volume,
client, frames_written, mix_params, total_frames_written, prev_position. */
owned_critical_section stream_reset_lock;
/* Maximum number of frames that can be passed down in a callback. */
- uint32_t input_buffer_frame_count;
+ uint32_t input_buffer_frame_count = 0;
/* Maximum number of frames that can be requested in a callback. */
- uint32_t output_buffer_frame_count;
+ uint32_t output_buffer_frame_count = 0;
/* Resampler instance. Resampling will only happen if necessary. */
- cubeb_resampler * resampler;
- /* A buffer for up/down mixing multi-channel audio. */
- float * mix_buffer;
+ std::unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)>
+ resampler = {nullptr, cubeb_resampler_destroy};
+ /* Mixer interfaces */
+ std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> output_mixer = {
+ nullptr, cubeb_mixer_destroy};
+ std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> input_mixer = {
+ nullptr, cubeb_mixer_destroy};
+ /* A buffer for up/down mixing multi-channel audio output. */
+ std::vector<BYTE> mix_buffer;
/* WASAPI input works in "packets". We re-linearize the audio packets
* into this buffer before handing it to the resampler. */
- auto_array<float> linear_input_buffer;
+ std::unique_ptr<auto_array_wrapper> linear_input_buffer;
+ /* Bytes per sample. This multiplied by the number of channels is the number
+ * of bytes per frame. */
+ size_t bytes_per_sample = 0;
+ /* WAVEFORMATEXTENSIBLE sub-format: either PCM or float. */
+ GUID waveformatextensible_sub_format = GUID_NULL;
/* Stream volume. Set via stream_set_volume and used to reset volume on
device changes. */
- float volume;
+ float volume = 1.0;
/* True if the stream is draining. */
- bool draining;
+ bool draining = false;
/* True when we've destroyed the stream. This pointer is leaked on stream
* destruction if we could not join the thread. */
- std::atomic<std::atomic<bool>*> emergency_bailout;
+ std::atomic<std::atomic<bool> *> emergency_bailout{nullptr};
+ /* Synchronizes render thread start to ensure safe access to
+ * emergency_bailout. */
+ HANDLE thread_ready_event = 0;
+ /* This needs an active audio input stream to be known, and is updated in the
+ * first audio input callback. */
+ std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET};
+
+ /* Those attributes count the number of frames requested (resp. received) by
+ the OS, to be able to detect drifts. This is only used for logging for now. */
+ size_t total_input_frames = 0;
+ size_t total_output_frames = 0;
};
-class wasapi_endpoint_notification_client : public IMMNotificationClient
-{
+class monitor_device_notifications {
+public:
+ monitor_device_notifications(cubeb * context) : cubeb_context(context)
+ {
+ create_thread();
+ }
+
+ ~monitor_device_notifications()
+ {
+ SetEvent(begin_shutdown);
+ WaitForSingleObject(shutdown_complete, INFINITE);
+ CloseHandle(thread);
+
+ CloseHandle(input_changed);
+ CloseHandle(output_changed);
+ CloseHandle(begin_shutdown);
+ CloseHandle(shutdown_complete);
+ }
+
+ void notify(EDataFlow flow)
+ {
+ XASSERT(cubeb_context);
+ if (flow == eCapture && cubeb_context->input_collection_changed_callback) {
+ bool res = SetEvent(input_changed);
+ if (!res) {
+ LOG("Failed to set input changed event");
+ }
+ return;
+ }
+ if (flow == eRender && cubeb_context->output_collection_changed_callback) {
+ bool res = SetEvent(output_changed);
+ if (!res) {
+ LOG("Failed to set output changed event");
+ }
+ }
+ }
+
+private:
+ static unsigned int __stdcall thread_proc(LPVOID args)
+ {
+ XASSERT(args);
+ auto mdn = static_cast<monitor_device_notifications *>(args);
+ mdn->notification_thread_loop();
+ SetEvent(mdn->shutdown_complete);
+ return 0;
+ }
+
+ void notification_thread_loop()
+ {
+ struct auto_com {
+ auto_com()
+ {
+ HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ XASSERT(SUCCEEDED(hr));
+ }
+ ~auto_com() { CoUninitialize(); }
+ } com;
+
+ HANDLE wait_array[3] = {
+ input_changed,
+ output_changed,
+ begin_shutdown,
+ };
+
+ while (true) {
+ Sleep(200);
+
+ DWORD wait_result = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
+ wait_array, FALSE, INFINITE);
+ if (wait_result == WAIT_OBJECT_0) { // input changed
+ cubeb_context->input_collection_changed_callback(
+ cubeb_context, cubeb_context->input_collection_changed_user_ptr);
+ } else if (wait_result == WAIT_OBJECT_0 + 1) { // output changed
+ cubeb_context->output_collection_changed_callback(
+ cubeb_context, cubeb_context->output_collection_changed_user_ptr);
+ } else if (wait_result == WAIT_OBJECT_0 + 2) { // shutdown
+ break;
+ } else {
+ LOG("Unexpected result %lu", wait_result);
+ }
+ } // loop
+ }
+
+ void create_thread()
+ {
+ output_changed = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!output_changed) {
+ LOG("Failed to create output changed event.");
+ return;
+ }
+
+ input_changed = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!input_changed) {
+ LOG("Failed to create input changed event.");
+ return;
+ }
+
+ begin_shutdown = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!begin_shutdown) {
+ LOG("Failed to create begin_shutdown event.");
+ return;
+ }
+
+ shutdown_complete = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!shutdown_complete) {
+ LOG("Failed to create shutdown_complete event.");
+ return;
+ }
+
+ thread = (HANDLE)_beginthreadex(nullptr, 256 * 1024, thread_proc, this,
+ STACK_SIZE_PARAM_IS_A_RESERVATION, nullptr);
+ if (!thread) {
+ LOG("Failed to create thread.");
+ return;
+ }
+ }
+
+ HANDLE thread = INVALID_HANDLE_VALUE;
+ HANDLE output_changed = INVALID_HANDLE_VALUE;
+ HANDLE input_changed = INVALID_HANDLE_VALUE;
+ HANDLE begin_shutdown = INVALID_HANDLE_VALUE;
+ HANDLE shutdown_complete = INVALID_HANDLE_VALUE;
+
+ cubeb * cubeb_context = nullptr;
+};
+
+class wasapi_collection_notification_client : public IMMNotificationClient {
public:
/* The implementation of MSCOM was copied from MSDN. */
- ULONG STDMETHODCALLTYPE
- AddRef()
+ ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&ref_count); }
+
+ ULONG STDMETHODCALLTYPE Release()
{
- return InterlockedIncrement(&ref_count);
+ ULONG ulRef = InterlockedDecrement(&ref_count);
+ if (0 == ulRef) {
+ delete this;
+ }
+ return ulRef;
}
- ULONG STDMETHODCALLTYPE
- Release()
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID ** ppvInterface)
+ {
+ if (__uuidof(IUnknown) == riid) {
+ AddRef();
+ *ppvInterface = (IUnknown *)this;
+ } else if (__uuidof(IMMNotificationClient) == riid) {
+ AddRef();
+ *ppvInterface = (IMMNotificationClient *)this;
+ } else {
+ *ppvInterface = NULL;
+ return E_NOINTERFACE;
+ }
+ return S_OK;
+ }
+
+ wasapi_collection_notification_client(cubeb * context)
+ : ref_count(1), cubeb_context(context), monitor_notifications(context)
+ {
+ XASSERT(cubeb_context);
+ }
+
+ virtual ~wasapi_collection_notification_client() {}
+
+ HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
+ LPCWSTR device_id)
+ {
+ LOG("collection: Audio device default changed, id = %S.", device_id);
+ return S_OK;
+ }
+
+ /* The remaining methods are not implemented, they simply log when called (if
+ log is enabled), for debugging. */
+ HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
+ {
+ LOG("collection: Audio device added.");
+ return S_OK;
+ };
+
+ HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
+ {
+ LOG("collection: Audio device removed.");
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id,
+ DWORD new_state)
+ {
+ XASSERT(cubeb_context->output_collection_changed_callback ||
+ cubeb_context->input_collection_changed_callback);
+ LOG("collection: Audio device state changed, id = %S, state = %lu.",
+ device_id, new_state);
+ EDataFlow flow;
+ HRESULT hr = GetDataFlow(device_id, &flow);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ monitor_notifications.notify(flow);
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id,
+ const PROPERTYKEY key)
+ {
+ // Audio device property value changed.
+ return S_OK;
+ }
+
+private:
+ HRESULT GetDataFlow(LPCWSTR device_id, EDataFlow * flow)
+ {
+ com_ptr<IMMDevice> device;
+ com_ptr<IMMEndpoint> endpoint;
+
+ HRESULT hr = cubeb_context->device_collection_enumerator->GetDevice(
+ device_id, device.receive());
+ if (FAILED(hr)) {
+ LOG("collection: Could not get device: %lx", hr);
+ return hr;
+ }
+
+ hr = device->QueryInterface(IID_PPV_ARGS(endpoint.receive()));
+ if (FAILED(hr)) {
+ LOG("collection: Could not get endpoint: %lx", hr);
+ return hr;
+ }
+
+ return endpoint->GetDataFlow(flow);
+ }
+
+ /* refcount for this instance, necessary to implement MSCOM semantics. */
+ LONG ref_count;
+
+ cubeb * cubeb_context = nullptr;
+ monitor_device_notifications monitor_notifications;
+};
+
+class wasapi_endpoint_notification_client : public IMMNotificationClient {
+public:
+ /* The implementation of MSCOM was copied from MSDN. */
+ ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&ref_count); }
+
+ ULONG STDMETHODCALLTYPE Release()
{
ULONG ulRef = InterlockedDecrement(&ref_count);
if (0 == ulRef) {
@@ -246,15 +633,14 @@ public:
return ulRef;
}
- HRESULT STDMETHODCALLTYPE
- QueryInterface(REFIID riid, VOID **ppvInterface)
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID ** ppvInterface)
{
if (__uuidof(IUnknown) == riid) {
AddRef();
- *ppvInterface = (IUnknown*)this;
+ *ppvInterface = (IUnknown *)this;
} else if (__uuidof(IMMNotificationClient) == riid) {
AddRef();
- *ppvInterface = (IMMNotificationClient*)this;
+ *ppvInterface = (IMMNotificationClient *)this;
} else {
*ppvInterface = NULL;
return E_NOINTERFACE;
@@ -262,27 +648,27 @@ public:
return S_OK;
}
- wasapi_endpoint_notification_client(HANDLE event)
- : ref_count(1)
- , reconfigure_event(event)
- { }
+ wasapi_endpoint_notification_client(HANDLE event, ERole role)
+ : ref_count(1), reconfigure_event(event), role(role)
+ {
+ }
- virtual ~wasapi_endpoint_notification_client()
- { }
+ virtual ~wasapi_endpoint_notification_client() {}
- HRESULT STDMETHODCALLTYPE
- OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id)
+ HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
+ LPCWSTR device_id)
{
- LOG("Audio device default changed.");
+ LOG("endpoint: Audio device default changed.");
/* we only support a single stream type for now. */
- if (flow != eRender && role != eConsole) {
+ if (flow != eRender && role != this->role) {
return S_OK;
}
BOOL ok = SetEvent(reconfigure_event);
if (!ok) {
- LOG("SetEvent on reconfigure_event failed: %x", GetLastError());
+ LOG("endpoint: SetEvent on reconfigure_event failed: %lx",
+ GetLastError());
}
return S_OK;
@@ -292,163 +678,123 @@ public:
log is enabled), for debugging. */
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
{
- LOG("Audio device added.");
+ LOG("endpoint: Audio device added.");
return S_OK;
};
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
{
- LOG("Audio device removed.");
+ LOG("endpoint: Audio device removed.");
return S_OK;
}
- HRESULT STDMETHODCALLTYPE
- OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state)
+ HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id,
+ DWORD new_state)
{
- LOG("Audio device state changed.");
+ LOG("endpoint: Audio device state changed.");
return S_OK;
}
- HRESULT STDMETHODCALLTYPE
- OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key)
+ HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id,
+ const PROPERTYKEY key)
{
- LOG("Audio device property value changed.");
+ // Audio device property value changed.
return S_OK;
}
+
private:
/* refcount for this instance, necessary to implement MSCOM semantics. */
LONG ref_count;
HANDLE reconfigure_event;
+ ERole role;
};
namespace {
-bool has_input(cubeb_stream * stm)
-{
- return stm->input_stream_params.rate != 0;
-}
-bool has_output(cubeb_stream * stm)
+char const *
+intern_device_id(cubeb * ctx, wchar_t const * id)
{
- return stm->output_stream_params.rate != 0;
+ XASSERT(id);
+
+ char const * tmp = wstr_to_utf8(id);
+ if (!tmp) {
+ return nullptr;
+ }
+
+ char const * interned = cubeb_strings_intern(ctx->device_ids, tmp);
+
+ free((void *)tmp);
+
+ return interned;
}
-bool should_upmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+bool
+has_input(cubeb_stream * stm)
{
- return mixer.channels > stream.channels;
+ return stm->input_stream_params.rate != 0;
}
-bool should_downmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+bool
+has_output(cubeb_stream * stm)
{
- return mixer.channels < stream.channels;
+ return stm->output_stream_params.rate != 0;
}
-double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer)
+double
+stream_to_mix_samplerate_ratio(cubeb_stream_params & stream,
+ cubeb_stream_params & mixer)
{
return double(stream.rate) / mixer.rate;
}
+/* Convert the channel layout into the corresponding KSAUDIO_CHANNEL_CONFIG.
+ See more:
+ https://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx
+ */
-uint32_t
-get_rate(cubeb_stream * stm)
+cubeb_channel_layout
+mask_to_channel_layout(WAVEFORMATEX const * fmt)
{
- return has_input(stm) ? stm->input_stream_params.rate
- : stm->output_stream_params.rate;
+ cubeb_channel_layout mask = 0;
+
+ if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ WAVEFORMATEXTENSIBLE const * ext =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE const *>(fmt);
+ mask = ext->dwChannelMask;
+ } else if (fmt->wFormatTag == WAVE_FORMAT_PCM ||
+ fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
+ if (fmt->nChannels == 1) {
+ mask = CHANNEL_FRONT_CENTER;
+ } else if (fmt->nChannels == 2) {
+ mask = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT;
+ }
+ }
+ return mask;
}
uint32_t
-ms_to_hns(uint32_t ms)
+get_rate(cubeb_stream * stm)
{
- return ms * 10000;
+ return has_input(stm) ? stm->input_stream_params.rate
+ : stm->output_stream_params.rate;
}
uint32_t
-hns_to_ms(REFERENCE_TIME hns)
-{
- return static_cast<uint32_t>(hns / 10000);
-}
-
-double
-hns_to_s(REFERENCE_TIME hns)
+hns_to_frames(uint32_t rate, REFERENCE_TIME hns)
{
- return static_cast<double>(hns) / 10000000;
+ return std::ceil((hns - 1) / 10000000.0 * rate);
}
uint32_t
hns_to_frames(cubeb_stream * stm, REFERENCE_TIME hns)
{
- return hns_to_ms(hns * get_rate(stm)) / 1000;
-}
-
-uint32_t
-hns_to_frames(uint32_t rate, REFERENCE_TIME hns)
-{
- return hns_to_ms(hns * rate) / 1000;
+ return hns_to_frames(get_rate(stm), hns);
}
REFERENCE_TIME
-frames_to_hns(cubeb_stream * stm, uint32_t frames)
-{
- return frames * 1000 / get_rate(stm);
-}
-
-/* Upmix function, copies a mono channel into L and R */
-template<typename T>
-void
-mono_to_stereo(T * in, long insamples, T * out, int32_t out_channels)
-{
- for (int i = 0, j = 0; i < insamples; ++i, j += out_channels) {
- out[j] = out[j + 1] = in[i];
- }
-}
-
-template<typename T>
-void
-upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
-{
- XASSERT(out_channels >= in_channels && in_channels > 0);
-
- /* Either way, if we have 2 or more channels, the first two are L and R. */
- /* If we are playing a mono stream over stereo speakers, copy the data over. */
- if (in_channels == 1 && out_channels >= 2) {
- mono_to_stereo(in, inframes, out, out_channels);
- } else {
- /* Copy through. */
- for (int i = 0, o = 0; i < inframes * in_channels;
- i += in_channels, o += out_channels) {
- for (int j = 0; j < in_channels; ++j) {
- out[o + j] = in[i + j];
- }
- }
- }
-
- /* Check if more channels. */
- if (out_channels <= 2) {
- return;
- }
-
- /* Put silence in remaining channels. */
- for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
- for (int j = 2; j < out_channels; ++j) {
- out[o + j] = 0.0;
- }
- }
-}
-
-template<typename T>
-void
-downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
+frames_to_hns(uint32_t rate, uint32_t frames)
{
- XASSERT(in_channels >= out_channels);
- /* We could use a downmix matrix here, applying mixing weight based on the
- channel, but directsound and winmm simply drop the channels that cannot be
- rendered by the hardware, so we do the same for consistency. */
- long out_index = 0;
- for (long i = 0; i < inframes * in_channels; i += in_channels) {
- for (int j = 0; j < out_channels; ++j) {
- out[out_index + j] = in[i + j];
- }
- out_index += out_channels;
- }
+ return std::ceil(frames * 10000000.0 / rate);
}
/* This returns the size of a frame in the stream, before the eventual upmix
@@ -456,157 +802,240 @@ downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channel
static size_t
frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
{
- size_t stream_frame_size = stm->output_stream_params.channels * sizeof(float);
- return stream_frame_size * frames;
+ // This is called only when we has a output client.
+ XASSERT(has_output(stm));
+ return stm->output_stream_params.channels * stm->bytes_per_sample * frames;
}
/* This function handles the processing of the input and output audio,
* converting it to rate and channel layout specified at initialization.
* It then calls the data callback, via the resampler. */
long
-refill(cubeb_stream * stm, float * input_buffer, long input_frames_count,
- float * output_buffer, long output_frames_needed)
+refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
+ void * output_buffer, long output_frames_needed)
{
+ XASSERT(!stm->draining);
/* If we need to upmix after resampling, resample into the mix buffer to
- avoid a copy. */
- float * dest = nullptr;
- if (has_output(stm)) {
- if (should_upmix(stm->output_stream_params, stm->output_mix_params) ||
- should_downmix(stm->output_stream_params, stm->output_mix_params)) {
- dest = stm->mix_buffer;
+ avoid a copy. Avoid exposing output if it is a dummy stream. */
+ void * dest = nullptr;
+ if (has_output(stm) && !stm->has_dummy_output) {
+ if (stm->output_mixer) {
+ dest = stm->mix_buffer.data();
} else {
dest = output_buffer;
}
}
- long out_frames = cubeb_resampler_fill(stm->resampler,
- input_buffer,
- &input_frames_count,
- dest,
- output_frames_needed);
+ long out_frames =
+ cubeb_resampler_fill(stm->resampler.get(), input_buffer,
+ &input_frames_count, dest, output_frames_needed);
/* TODO: Report out_frames < 0 as an error via the API. */
XASSERT(out_frames >= 0);
+ float volume = 1.0;
{
auto_lock lock(stm->stream_reset_lock);
stm->frames_written += out_frames;
+ volume = stm->volume;
}
- /* Go in draining mode if we got fewer frames than requested. */
- if (out_frames < output_frames_needed) {
+ /* Go in draining mode if we got fewer frames than requested. If the stream
+ has no output we still expect the callback to return number of frames read
+ from input, otherwise we stop. */
+ if ((out_frames < output_frames_needed) ||
+ (!has_output(stm) && out_frames < input_frames_count)) {
LOG("start draining.");
stm->draining = true;
}
/* If this is not true, there will be glitches.
It is alright to have produced less frames if we are draining, though. */
- XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm));
+ XASSERT(out_frames == output_frames_needed || stm->draining ||
+ !has_output(stm) || stm->has_dummy_output);
+
+#ifndef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+ if (has_output(stm) && !stm->has_dummy_output && volume != 1.0) {
+ // Adjust the output volume.
+ // Note: This could be integrated with the remixing below.
+ long out_samples = out_frames * stm->output_stream_params.channels;
+ if (volume == 0.0) {
+ memset(dest, 0, out_samples * stm->bytes_per_sample);
+ } else {
+ switch (stm->output_stream_params.format) {
+ case CUBEB_SAMPLE_FLOAT32NE: {
+ float * buf = static_cast<float *>(dest);
+ for (long i = 0; i < out_samples; ++i) {
+ buf[i] *= volume;
+ }
+ break;
+ }
+ case CUBEB_SAMPLE_S16NE: {
+ short * buf = static_cast<short *>(dest);
+ for (long i = 0; i < out_samples; ++i) {
+ buf[i] = static_cast<short>(static_cast<float>(buf[i]) * volume);
+ }
+ break;
+ }
+ default:
+ XASSERT(false);
+ }
+ }
+ }
+#endif
- if (has_output(stm)) {
- if (should_upmix(stm->output_stream_params, stm->output_mix_params)) {
- upmix(dest, out_frames, output_buffer,
- stm->output_stream_params.channels, stm->output_mix_params.channels);
- } else if (should_downmix(stm->output_stream_params, stm->output_mix_params)) {
- downmix(dest, out_frames, output_buffer,
- stm->output_stream_params.channels, stm->output_mix_params.channels);
+ // We don't bother mixing dummy output as it will be silenced, otherwise mix
+ // output if needed
+ if (!stm->has_dummy_output && has_output(stm) && stm->output_mixer) {
+ XASSERT(dest == stm->mix_buffer.data());
+ size_t dest_size =
+ out_frames * stm->output_stream_params.channels * stm->bytes_per_sample;
+ XASSERT(dest_size <= stm->mix_buffer.size());
+ size_t output_buffer_size =
+ out_frames * stm->output_mix_params.channels * stm->bytes_per_sample;
+ int ret = cubeb_mixer_mix(stm->output_mixer.get(), out_frames, dest,
+ dest_size, output_buffer, output_buffer_size);
+ if (ret < 0) {
+ LOG("Error remixing content (%d)", ret);
}
}
return out_frames;
}
-/* This helper grabs all the frames available from a capture client, put them in
- * linear_input_buffer. linear_input_buffer should be cleared before the
- * callback exits. */
-bool get_input_buffer(cubeb_stream * stm)
+int
+trigger_async_reconfigure(cubeb_stream * stm)
{
- HRESULT hr;
- UINT32 padding_in;
+ XASSERT(stm && stm->reconfigure_event);
+ BOOL ok = SetEvent(stm->reconfigure_event);
+ if (!ok) {
+ LOG("SetEvent on reconfigure_event failed: %lx", GetLastError());
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+/* This helper grabs all the frames available from a capture client, put them in
+ * the linear_input_buffer. This helper does not work with exclusive mode
+ * streams. */
+bool
+get_input_buffer(cubeb_stream * stm)
+{
XASSERT(has_input(stm));
- hr = stm->input_client->GetCurrentPadding(&padding_in);
- if (FAILED(hr)) {
- LOG("Failed to get padding");
- return false;
- }
- XASSERT(padding_in <= stm->input_buffer_frame_count);
- UINT32 total_available_input = padding_in;
-
+ HRESULT hr;
BYTE * input_packet = NULL;
DWORD flags;
UINT64 dev_pos;
+ UINT64 pc_position;
UINT32 next;
/* Get input packets until we have captured enough frames, and put them in a
* contiguous buffer. */
uint32_t offset = 0;
- while (offset != total_available_input) {
- hr = stm->capture_client->GetNextPacketSize(&next);
+ // If the input stream is event driven we should only ever expect to read a
+ // single packet each time. However, if we're pulling from the stream we may
+ // need to grab multiple packets worth of frames that have accumulated (so
+ // need a loop).
+ for (hr = stm->capture_client->GetNextPacketSize(&next); next > 0;
+ hr = stm->capture_client->GetNextPacketSize(&next)) {
+ if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
+ // Application can recover from this error. More info
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
+ LOG("Device invalidated error, reset default device");
+ trigger_async_reconfigure(stm);
+ return true;
+ }
+
if (FAILED(hr)) {
- LOG("cannot get next packet size: %x", hr);
+ LOG("cannot get next packet size: %lx", hr);
return false;
}
- /* This can happen if the capture stream has stopped. Just return in this
- * case. */
- if (!next) {
- break;
- }
- UINT32 packet_size;
- hr = stm->capture_client->GetBuffer(&input_packet,
- &packet_size,
- &flags,
- &dev_pos,
- NULL);
+ UINT32 frames;
+ hr = stm->capture_client->GetBuffer(&input_packet, &frames, &flags,
+ &dev_pos, &pc_position);
+
if (FAILED(hr)) {
- LOG("GetBuffer failed for capture: %x", hr);
+ LOG("GetBuffer failed for capture: %lx", hr);
return false;
}
- XASSERT(packet_size == next);
+ XASSERT(frames == next);
+
+ if (stm->context->performance_counter_frequency) {
+ LARGE_INTEGER now;
+ UINT64 now_hns;
+ // See
+ // https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudiocaptureclient-getbuffer,
+ // section "Remarks".
+ QueryPerformanceCounter(&now);
+ now_hns =
+ 10000000 * now.QuadPart / stm->context->performance_counter_frequency;
+ if (now_hns >= pc_position) {
+ stm->input_latency_hns = now_hns - pc_position;
+ }
+ }
+
+ stm->total_input_frames += frames;
+
+ UINT32 input_stream_samples = frames * stm->input_stream_params.channels;
+ // We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY
+ // flag. There a two primary (non exhaustive) scenarios we anticipate this
+ // flag being set in:
+ // - The first GetBuffer after Start has this flag undefined. In this
+ // case the flag may be set but is meaningless and can be ignored.
+ // - If a glitch is introduced into the input. This should not happen
+ // for event based inputs, and should be mitigated by using a dummy
+ // stream to drive input in the case of input only loopback. Without
+ // a dummy output, input only loopback would glitch on silence. However,
+ // the dummy input should push silence to the loopback and prevent
+ // discontinuities. See
+ // https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/
+ // As the first scenario can be ignored, and we anticipate the second
+ // scenario is mitigated, we ignore the flag.
+ // For more info:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd370859(v=vs.85).aspx,
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd371458(v=vs.85).aspx
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
- LOG("insert silence: ps=%u", packet_size);
- stm->linear_input_buffer.push_silence(packet_size * stm->input_stream_params.channels);
+ LOG("insert silence: ps=%u", frames);
+ stm->linear_input_buffer->push_silence(input_stream_samples);
} else {
- if (should_upmix(stm->input_mix_params, stm->input_stream_params)) {
- bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
- packet_size * stm->input_stream_params.channels);
- assert(ok);
- upmix(reinterpret_cast<float*>(input_packet), packet_size,
- stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
- stm->input_mix_params.channels,
- stm->input_stream_params.channels);
- stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
- } else if (should_downmix(stm->input_mix_params, stm->input_stream_params)) {
- bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
- packet_size * stm->input_stream_params.channels);
- assert(ok);
- downmix(reinterpret_cast<float*>(input_packet), packet_size,
- stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
- stm->input_mix_params.channels,
- stm->input_stream_params.channels);
- stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
+ if (stm->input_mixer) {
+ bool ok = stm->linear_input_buffer->reserve(
+ stm->linear_input_buffer->length() + input_stream_samples);
+ XASSERT(ok);
+ size_t input_packet_size =
+ frames * stm->input_mix_params.channels *
+ cubeb_sample_size(stm->input_mix_params.format);
+ size_t linear_input_buffer_size =
+ input_stream_samples *
+ cubeb_sample_size(stm->input_stream_params.format);
+ cubeb_mixer_mix(stm->input_mixer.get(), frames, input_packet,
+ input_packet_size, stm->linear_input_buffer->end(),
+ linear_input_buffer_size);
+ stm->linear_input_buffer->set_length(
+ stm->linear_input_buffer->length() + input_stream_samples);
} else {
- stm->linear_input_buffer.push(reinterpret_cast<float*>(input_packet),
- packet_size * stm->input_stream_params.channels);
+ stm->linear_input_buffer->push(input_packet, input_stream_samples);
}
}
- hr = stm->capture_client->ReleaseBuffer(packet_size);
+ hr = stm->capture_client->ReleaseBuffer(frames);
if (FAILED(hr)) {
LOG("FAILED to release intput buffer");
return false;
}
- offset += packet_size;
+ offset += input_stream_samples;
}
- assert(stm->linear_input_buffer.length() >= total_available_input &&
- offset == total_available_input);
+ ALOGV("get_input_buffer: got %d frames", offset);
+
+ XASSERT(stm->linear_input_buffer->length() >= offset);
return true;
}
/* Get an output buffer from the render_client. It has to be released before
* exiting the callback. */
-bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count)
+bool
+get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
{
UINT32 padding_out;
HRESULT hr;
@@ -614,10 +1043,19 @@ bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count
XASSERT(has_output(stm));
hr = stm->output_client->GetCurrentPadding(&padding_out);
+ if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
+ // Application can recover from this error. More info
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
+ LOG("Device invalidated error, reset default device");
+ trigger_async_reconfigure(stm);
+ return true;
+ }
+
if (FAILED(hr)) {
- LOG("Failed to get padding: %x", hr);
+ LOG("Failed to get padding: %lx", hr);
return false;
}
+
XASSERT(padding_out <= stm->output_buffer_frame_count);
if (stm->draining) {
@@ -639,7 +1077,7 @@ bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count
return false;
}
- buffer = reinterpret_cast<float*>(output_buffer);
+ buffer = output_buffer;
return true;
}
@@ -651,22 +1089,22 @@ bool
refill_callback_duplex(cubeb_stream * stm)
{
HRESULT hr;
- float * output_buffer = nullptr;
+ void * output_buffer = nullptr;
size_t output_frames = 0;
size_t input_frames;
bool rv;
XASSERT(has_input(stm) && has_output(stm));
- rv = get_input_buffer(stm);
- if (!rv) {
- return rv;
+ if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ HRESULT rv = get_input_buffer(stm);
+ if (FAILED(rv)) {
+ return rv;
+ }
}
- input_frames = stm->linear_input_buffer.length() / stm->input_stream_params.channels;
- if (!input_frames) {
- return true;
- }
+ input_frames =
+ stm->linear_input_buffer->length() / stm->input_stream_params.channels;
rv = get_output_buffer(stm, output_buffer, output_frames);
if (!rv) {
@@ -680,29 +1118,45 @@ refill_callback_duplex(cubeb_stream * stm)
return true;
}
- // When WASAPI has not filled the input buffer yet, send silence.
- double output_duration = double(output_frames) / stm->output_mix_params.rate;
- double input_duration = double(stm->linear_input_buffer.length() / stm->input_stream_params.channels) / stm->input_mix_params.rate;
- if (input_duration < output_duration) {
- size_t padding = size_t(round((output_duration - input_duration) * stm->input_mix_params.rate));
- LOG("padding silence: out=%f in=%f pad=%u", output_duration, input_duration, padding);
- stm->linear_input_buffer.push_front_silence(padding * stm->input_stream_params.channels);
+ /* Wait for draining is not important on duplex. */
+ if (stm->draining) {
+ return false;
}
- LOGV("Duplex callback: input frames: %zu, output frames: %zu",
- stm->linear_input_buffer.length(), output_frames);
+ stm->total_output_frames += output_frames;
+
+ ALOGV("in: %zu, out: %zu, missing: %ld, ratio: %f", stm->total_input_frames,
+ stm->total_output_frames,
+ static_cast<long>(stm->total_output_frames) - stm->total_input_frames,
+ static_cast<float>(stm->total_output_frames) / stm->total_input_frames);
+
+ if (stm->has_dummy_output) {
+ ALOGV(
+ "Duplex callback (dummy output): input frames: %Iu, output frames: %Iu",
+ input_frames, output_frames);
- refill(stm,
- stm->linear_input_buffer.data(),
- stm->linear_input_buffer.length(),
- output_buffer,
- output_frames);
+ // We don't want to expose the dummy output to the callback so don't pass
+ // the output buffer (it will be released later with silence in it)
+ refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
+ } else {
+ ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
+ input_frames, output_frames);
+
+ refill(stm, stm->linear_input_buffer->data(), input_frames, output_buffer,
+ output_frames);
+ }
- stm->linear_input_buffer.clear();
+ stm->linear_input_buffer->clear();
- hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+ if (stm->has_dummy_output) {
+ // If output is a dummy output, make sure it's silent
+ hr = stm->render_client->ReleaseBuffer(output_frames,
+ AUDCLNT_BUFFERFLAGS_SILENT);
+ } else {
+ hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+ }
if (FAILED(hr)) {
- LOG("failed to release buffer: %x", hr);
+ LOG("failed to release buffer: %lx", hr);
return false;
}
return true;
@@ -711,7 +1165,8 @@ refill_callback_duplex(cubeb_stream * stm)
bool
refill_callback_input(cubeb_stream * stm)
{
- bool rv, consumed_all_buffer;
+ bool rv;
+ size_t input_frames;
XASSERT(has_input(stm) && !has_output(stm));
@@ -720,24 +1175,22 @@ refill_callback_input(cubeb_stream * stm)
return rv;
}
- // This can happen at the very beginning of the stream.
- if (!stm->linear_input_buffer.length()) {
+ input_frames =
+ stm->linear_input_buffer->length() / stm->input_stream_params.channels;
+ if (!input_frames) {
return true;
}
- LOGV("Input callback: input frames: %zu", stm->linear_input_buffer.length());
+ ALOGV("Input callback: input frames: %Iu", input_frames);
- long read = refill(stm,
- stm->linear_input_buffer.data(),
- stm->linear_input_buffer.length(),
- nullptr,
- 0);
+ long read =
+ refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
- consumed_all_buffer = read == stm->linear_input_buffer.length();
+ XASSERT(read >= 0);
- stm->linear_input_buffer.clear();
+ stm->linear_input_buffer->clear();
- return consumed_all_buffer;
+ return !stm->draining;
}
bool
@@ -745,7 +1198,7 @@ refill_callback_output(cubeb_stream * stm)
{
bool rv;
HRESULT hr;
- float * output_buffer = nullptr;
+ void * output_buffer = nullptr;
size_t output_frames = 0;
XASSERT(!has_input(stm) && has_output(stm));
@@ -759,62 +1212,57 @@ refill_callback_output(cubeb_stream * stm)
return true;
}
- long got = refill(stm,
- nullptr,
- 0,
- output_buffer,
- output_frames);
+ long got = refill(stm, nullptr, 0, output_buffer, output_frames);
- LOGV("Output callback: output frames requested: %zu, got %ld",
- output_frames, got);
+ ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
+ got);
XASSERT(got >= 0);
- XASSERT(got == output_frames || stm->draining);
+ XASSERT(size_t(got) == output_frames || stm->draining);
hr = stm->render_client->ReleaseBuffer(got, 0);
if (FAILED(hr)) {
- LOG("failed to release buffer: %x", hr);
+ LOG("failed to release buffer: %lx", hr);
return false;
}
- return got == output_frames || stm->draining;
+ return size_t(got) == output_frames || stm->draining;
}
-static unsigned int __stdcall
-wasapi_stream_render_loop(LPVOID stream)
+static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
{
cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
std::atomic<bool> * emergency_bailout = stm->emergency_bailout;
+ // Signal wasapi_stream_start that we've copied emergency_bailout.
+ BOOL ok = SetEvent(stm->thread_ready_event);
+ if (!ok) {
+ LOG("thread_ready SetEvent failed: %lx", GetLastError());
+ return 0;
+ }
+
bool is_playing = true;
- HANDLE wait_array[4] = {
- stm->shutdown_event,
- stm->reconfigure_event,
- stm->refill_event,
- stm->input_available_event
- };
+ HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
+ stm->refill_event, stm->input_available_event};
HANDLE mmcss_handle = NULL;
HRESULT hr = 0;
DWORD mmcss_task_index = 0;
- auto_com com;
- if (!com.ok()) {
- LOG("COM initialization failed on render_loop thread.");
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- return 0;
- }
+ struct auto_com {
+ auto_com()
+ {
+ HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ XASSERT(SUCCEEDED(hr));
+ }
+ ~auto_com() { CoUninitialize(); }
+ } com;
/* We could consider using "Pro Audio" here for WebAudio and
maybe WebRTC. */
- mmcss_handle =
- stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index);
+ mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index);
if (!mmcss_handle) {
/* This is not fatal, but we might glitch under heavy load. */
- LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError());
- }
-
- // This has already been nulled out, simply exit.
- if (!emergency_bailout) {
- is_playing = false;
+ LOG("Unable to use mmcss to bump the render thread priority: %lx",
+ GetLastError());
}
/* WaitForMultipleObjects timeout can trigger in cases where we don't want to
@@ -822,19 +1270,18 @@ wasapi_stream_render_loop(LPVOID stream)
the timeout error handling only when the timeout_limit is reached, which is
reset on each successful loop. */
unsigned timeout_count = 0;
- const unsigned timeout_limit = 5;
+ const unsigned timeout_limit = 3;
while (is_playing) {
// We want to check the emergency bailout variable before a
- // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects
- // is going to wait on might have been closed already.
+ // and after the WaitForMultipleObject, because the handles
+ // WaitForMultipleObjects is going to wait on might have been closed
+ // already.
if (*emergency_bailout) {
delete emergency_bailout;
return 0;
}
DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
- wait_array,
- FALSE,
- 1000);
+ wait_array, FALSE, 1000);
if (*emergency_bailout) {
delete emergency_bailout;
return 0;
@@ -868,8 +1315,8 @@ wasapi_stream_render_loop(LPVOID stream)
auto_lock lock(stm->stream_reset_lock);
close_wasapi_stream(stm);
LOG("Stream closed.");
- /* Reopen a stream and start it immediately. This will automatically pick the
- new default device for this role. */
+ /* Reopen a stream and start it immediately. This will automatically
+ pick the new default device for this role. */
int r = setup_wasapi_stream(stm);
if (r != CUBEB_OK) {
LOG("Error setting up the stream during reconfigure.");
@@ -883,24 +1330,42 @@ wasapi_stream_render_loop(LPVOID stream)
}
XASSERT(stm->output_client || stm->input_client);
if (stm->output_client) {
- stm->output_client->Start();
+ hr = stm->output_client->Start();
+ if (FAILED(hr)) {
+ LOG("Error starting output after reconfigure, error: %lx", hr);
+ is_playing = false;
+ continue;
+ }
LOG("Output started after reconfigure.");
}
if (stm->input_client) {
- stm->input_client->Start();
+ hr = stm->input_client->Start();
+ if (FAILED(hr)) {
+ LOG("Error starting input after reconfiguring, error: %lx", hr);
+ is_playing = false;
+ continue;
+ }
LOG("Input started after reconfigure.");
}
break;
}
- case WAIT_OBJECT_0 + 2: /* refill */
- XASSERT(has_input(stm) && has_output(stm) ||
- !has_input(stm) && has_output(stm));
+ case WAIT_OBJECT_0 + 2: /* refill */
+ XASSERT((has_input(stm) && has_output(stm)) ||
+ (!has_input(stm) && has_output(stm)));
is_playing = stm->refill_callback(stm);
break;
- case WAIT_OBJECT_0 + 3: /* input available */
- if (has_input(stm) && has_output(stm)) { continue; }
- is_playing = stm->refill_callback(stm);
+ case WAIT_OBJECT_0 + 3: { /* input available */
+ HRESULT rv = get_input_buffer(stm);
+ if (FAILED(rv)) {
+ return rv;
+ }
+
+ if (!has_output(stm)) {
+ is_playing = stm->refill_callback(stm);
+ }
+
break;
+ }
case WAIT_TIMEOUT:
XASSERT(stm->shutdown_event == wait_array[0]);
if (++timeout_count >= timeout_limit) {
@@ -910,7 +1375,7 @@ wasapi_stream_render_loop(LPVOID stream)
}
break;
default:
- LOG("case %d not handled in render loop.", waitResult);
+ LOG("case %lu not handled in render loop.", waitResult);
abort();
}
}
@@ -919,111 +1384,132 @@ wasapi_stream_render_loop(LPVOID stream)
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
}
- stm->context->revert_mm_thread_characteristics(mmcss_handle);
+ if (mmcss_handle) {
+ AvRevertMmThreadCharacteristics(mmcss_handle);
+ }
return 0;
}
-void wasapi_destroy(cubeb * context);
-
-HANDLE WINAPI set_mm_thread_characteristics_noop(const char *, LPDWORD mmcss_task_index)
-{
- return (HANDLE)1;
-}
-
-BOOL WINAPI revert_mm_thread_characteristics_noop(HANDLE mmcss_handle)
-{
- return true;
-}
+void
+wasapi_destroy(cubeb * context);
-HRESULT register_notification_client(cubeb_stream * stm)
+HRESULT
+register_notification_client(cubeb_stream * stm)
{
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL, CLSCTX_INPROC_SERVER,
- IID_PPV_ARGS(&stm->device_enumerator));
- if (FAILED(hr)) {
- LOG("Could not get device enumerator: %x", hr);
- return hr;
- }
+ XASSERT(stm->device_enumerator);
- stm->notification_client = new wasapi_endpoint_notification_client(stm->reconfigure_event);
+ stm->notification_client.reset(new wasapi_endpoint_notification_client(
+ stm->reconfigure_event, stm->role));
- hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client);
+ HRESULT hr = stm->device_enumerator->RegisterEndpointNotificationCallback(
+ stm->notification_client.get());
if (FAILED(hr)) {
- LOG("Could not register endpoint notification callback: %x", hr);
- SafeRelease(stm->notification_client);
+ LOG("Could not register endpoint notification callback: %lx", hr);
stm->notification_client = nullptr;
- SafeRelease(stm->device_enumerator);
- stm->device_enumerator = nullptr;
}
return hr;
}
-HRESULT unregister_notification_client(cubeb_stream * stm)
+HRESULT
+unregister_notification_client(cubeb_stream * stm)
{
- XASSERT(stm);
- HRESULT hr;
-
- if (!stm->device_enumerator) {
- return S_OK;
- }
+ XASSERT(stm->device_enumerator);
- hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client);
+ HRESULT hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(
+ stm->notification_client.get());
if (FAILED(hr)) {
// We can't really do anything here, we'll probably leak the
- // notification client, but we can at least release the enumerator.
- SafeRelease(stm->device_enumerator);
+ // notification client.
return S_OK;
}
- SafeRelease(stm->notification_client);
- SafeRelease(stm->device_enumerator);
+ stm->notification_client = nullptr;
return S_OK;
}
-HRESULT get_endpoint(IMMDevice ** device, LPCWSTR devid)
+HRESULT
+get_endpoint(com_ptr<IMMDevice> & device, LPCWSTR devid)
{
- IMMDeviceEnumerator * enumerator;
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL, CLSCTX_INPROC_SERVER,
- IID_PPV_ARGS(&enumerator));
+ com_ptr<IMMDeviceEnumerator> enumerator;
+ HRESULT hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(enumerator.receive()));
if (FAILED(hr)) {
- LOG("Could not get device enumerator: %x", hr);
+ LOG("Could not get device enumerator: %lx", hr);
return hr;
}
- hr = enumerator->GetDevice(devid, device);
+ hr = enumerator->GetDevice(devid, device.receive());
if (FAILED(hr)) {
- LOG("Could not get device: %x", hr);
- SafeRelease(enumerator);
+ LOG("Could not get device: %lx", hr);
return hr;
}
- SafeRelease(enumerator);
-
return S_OK;
}
-HRESULT get_default_endpoint(IMMDevice ** device, EDataFlow direction)
+HRESULT
+register_collection_notification_client(cubeb * context)
{
- IMMDeviceEnumerator * enumerator;
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL, CLSCTX_INPROC_SERVER,
- IID_PPV_ARGS(&enumerator));
+ HRESULT hr = CoCreateInstance(
+ __uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(context->device_collection_enumerator.receive()));
if (FAILED(hr)) {
- LOG("Could not get device enumerator: %x", hr);
+ LOG("Could not get device enumerator: %lx", hr);
return hr;
}
- hr = enumerator->GetDefaultAudioEndpoint(direction, eConsole, device);
+
+ context->collection_notification_client.reset(
+ new wasapi_collection_notification_client(context));
+
+ hr = context->device_collection_enumerator
+ ->RegisterEndpointNotificationCallback(
+ context->collection_notification_client.get());
+ if (FAILED(hr)) {
+ LOG("Could not register endpoint notification callback: %lx", hr);
+ context->collection_notification_client.reset();
+ context->device_collection_enumerator.reset();
+ }
+
+ return hr;
+}
+
+HRESULT
+unregister_collection_notification_client(cubeb * context)
+{
+ HRESULT hr = context->device_collection_enumerator
+ ->UnregisterEndpointNotificationCallback(
+ context->collection_notification_client.get());
if (FAILED(hr)) {
- LOG("Could not get default audio endpoint: %x", hr);
- SafeRelease(enumerator);
return hr;
}
- SafeRelease(enumerator);
+ context->collection_notification_client = nullptr;
+ context->device_collection_enumerator = nullptr;
+
+ return hr;
+}
+
+HRESULT
+get_default_endpoint(com_ptr<IMMDevice> & device, EDataFlow direction,
+ ERole role)
+{
+ com_ptr<IMMDeviceEnumerator> enumerator;
+ HRESULT hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(enumerator.receive()));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %lx", hr);
+ return hr;
+ }
+ hr = enumerator->GetDefaultAudioEndpoint(direction, role, device.receive());
+ if (FAILED(hr)) {
+ LOG("Could not get default audio endpoint: %lx", hr);
+ return hr;
+ }
return ERROR_SUCCESS;
}
@@ -1043,25 +1529,26 @@ current_stream_delay(cubeb_stream * stm)
UINT64 freq;
HRESULT hr = stm->audio_clock->GetFrequency(&freq);
if (FAILED(hr)) {
- LOG("GetFrequency failed: %x", hr);
+ LOG("GetFrequency failed: %lx", hr);
return 0;
}
UINT64 pos;
hr = stm->audio_clock->GetPosition(&pos, NULL);
if (FAILED(hr)) {
- LOG("GetPosition failed: %x", hr);
+ LOG("GetPosition failed: %lx", hr);
return 0;
}
double cur_pos = static_cast<double>(pos) / freq;
- double max_pos = static_cast<double>(stm->frames_written) / stm->output_mix_params.rate;
- double delay = max_pos - cur_pos;
- XASSERT(delay >= 0);
+ double max_pos =
+ static_cast<double>(stm->frames_written) / stm->output_mix_params.rate;
+ double delay = std::max(max_pos - cur_pos, 0.0);
return delay;
}
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
int
stream_set_volume(cubeb_stream * stm, float volume)
{
@@ -1073,8 +1560,8 @@ stream_set_volume(cubeb_stream * stm, float volume)
uint32_t channels;
HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels);
- if (hr != S_OK) {
- LOG("could not get the channel count: %x", hr);
+ if (FAILED(hr)) {
+ LOG("could not get the channel count: %lx", hr);
return CUBEB_ERROR;
}
@@ -1088,62 +1575,51 @@ stream_set_volume(cubeb_stream * stm, float volume)
volumes[i] = volume;
}
- hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes);
- if (hr != S_OK) {
- LOG("could not set the channels volume: %x", hr);
+ hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes);
+ if (FAILED(hr)) {
+ LOG("could not set the channels volume: %lx", hr);
return CUBEB_ERROR;
}
return CUBEB_OK;
}
-} // namespace anonymous
+#endif
+} // namespace
extern "C" {
-int wasapi_init(cubeb ** context, char const * context_name)
+int
+wasapi_init(cubeb ** context, char const * context_name)
{
- HRESULT hr;
- auto_com com;
- if (!com.ok()) {
- return CUBEB_ERROR;
- }
-
/* We don't use the device yet, but need to make sure we can initialize one
so that this backend is not incorrectly enabled on platforms that don't
support WASAPI. */
- IMMDevice * device;
- hr = get_default_endpoint(&device, eRender);
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, eConsole);
if (FAILED(hr)) {
- LOG("Could not get device: %x", hr);
- return CUBEB_ERROR;
+ XASSERT(hr != CO_E_NOTINITIALIZED);
+ LOG("It wasn't able to find a default rendering device: %lx", hr);
+ hr = get_default_endpoint(device, eCapture, eConsole);
+ if (FAILED(hr)) {
+ LOG("It wasn't able to find a default capture device: %lx", hr);
+ return CUBEB_ERROR;
+ }
}
- SafeRelease(device);
- cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb));
- if (!ctx) {
- return CUBEB_ERROR;
- }
+ cubeb * ctx = new cubeb();
ctx->ops = &wasapi_ops;
+ if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) {
+ delete ctx;
+ return CUBEB_ERROR;
+ }
- ctx->mmcss_module = LoadLibraryA("Avrt.dll");
-
- if (ctx->mmcss_module) {
- ctx->set_mm_thread_characteristics =
- (set_mm_thread_characteristics_function) GetProcAddress(
- ctx->mmcss_module, "AvSetMmThreadCharacteristicsA");
- ctx->revert_mm_thread_characteristics =
- (revert_mm_thread_characteristics_function) GetProcAddress(
- ctx->mmcss_module, "AvRevertMmThreadCharacteristics");
- if (!(ctx->set_mm_thread_characteristics && ctx->revert_mm_thread_characteristics)) {
- LOG("Could not load AvSetMmThreadCharacteristics or AvRevertMmThreadCharacteristics: %x", GetLastError());
- FreeLibrary(ctx->mmcss_module);
- }
+ LARGE_INTEGER frequency;
+ if (QueryPerformanceFrequency(&frequency)) {
+ ctx->performance_counter_frequency = frequency.QuadPart;
} else {
- // This is not a fatal error, but we might end up glitching when
- // the system is under high load.
- LOG("Could not load Avrt.dll");
- ctx->set_mm_thread_characteristics = &set_mm_thread_characteristics_noop;
- ctx->revert_mm_thread_characteristics = &revert_mm_thread_characteristics_noop;
+ LOG("Failed getting performance counter frequency, latency reporting will "
+ "be inacurate");
+ ctx->performance_counter_frequency = 0;
}
*context = ctx;
@@ -1153,7 +1629,8 @@ int wasapi_init(cubeb ** context, char const * context_name)
}
namespace {
-bool stop_and_join_render_thread(cubeb_stream * stm)
+bool
+stop_and_join_render_thread(cubeb_stream * stm)
{
bool rv = true;
LOG("Stop and join render thread.");
@@ -1170,31 +1647,23 @@ bool stop_and_join_render_thread(cubeb_stream * stm)
BOOL ok = SetEvent(stm->shutdown_event);
if (!ok) {
- LOG("Destroy SetEvent failed: %d", GetLastError());
+ LOG("Destroy SetEvent failed: %lx", GetLastError());
}
/* Wait five seconds for the rendering thread to return. It's supposed to
* check its event loop very often, five seconds is rather conservative. */
DWORD r = WaitForSingleObject(stm->thread, 5000);
- if (r == WAIT_TIMEOUT) {
+ if (r != WAIT_OBJECT_0) {
/* Something weird happened, leak the thread and continue the shutdown
* process. */
*(stm->emergency_bailout) = true;
// We give the ownership to the rendering thread.
stm->emergency_bailout = nullptr;
- LOG("Destroy WaitForSingleObject on thread timed out,"
- " leaking the thread: %d", GetLastError());
- rv = false;
- }
- if (r == WAIT_FAILED) {
- *(stm->emergency_bailout) = true;
- // We give the ownership to the rendering thread.
- stm->emergency_bailout = nullptr;
- LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError());
+ LOG("Destroy WaitForSingleObject on thread failed: %lx, %lx", r,
+ GetLastError());
rv = false;
}
-
// Only attempts to close and null out the thread and event if the
// WaitForSingleObject above succeeded, so that calling this function again
// attemps to clean up the thread and event each time.
@@ -1210,15 +1679,18 @@ bool stop_and_join_render_thread(cubeb_stream * stm)
return rv;
}
-void wasapi_destroy(cubeb * context)
+void
+wasapi_destroy(cubeb * context)
{
- if (context->mmcss_module) {
- FreeLibrary(context->mmcss_module);
+ if (context->device_ids) {
+ cubeb_strings_destroy(context->device_ids);
}
- free(context);
+
+ delete context;
}
-char const * wasapi_get_backend_id(cubeb * context)
+char const *
+wasapi_get_backend_id(cubeb * context)
{
return "wasapi";
}
@@ -1226,228 +1698,349 @@ char const * wasapi_get_backend_id(cubeb * context)
int
wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{
- HRESULT hr;
- IAudioClient * client;
- WAVEFORMATEX * mix_format;
- auto_com com;
- if (!com.ok()) {
- return CUBEB_ERROR;
- }
-
XASSERT(ctx && max_channels);
- IMMDevice * device;
- hr = get_default_endpoint(&device, eRender);
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, eConsole);
if (FAILED(hr)) {
return CUBEB_ERROR;
}
- hr = device->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL, (void **)&client);
- SafeRelease(device);
+ com_ptr<IAudioClient> client;
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ client.receive_vpp());
if (FAILED(hr)) {
return CUBEB_ERROR;
}
- hr = client->GetMixFormat(&mix_format);
+ WAVEFORMATEX * tmp = nullptr;
+ hr = client->GetMixFormat(&tmp);
if (FAILED(hr)) {
- SafeRelease(client);
return CUBEB_ERROR;
}
+ com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
*max_channels = mix_format->nChannels;
- CoTaskMemFree(mix_format);
- SafeRelease(client);
-
return CUBEB_OK;
}
int
-wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
+wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
{
- HRESULT hr;
- IAudioClient * client;
- REFERENCE_TIME default_period;
- auto_com com;
- if (!com.ok()) {
- return CUBEB_ERROR;
- }
-
- if (params.format != CUBEB_SAMPLE_FLOAT32NE) {
+ if (params.format != CUBEB_SAMPLE_FLOAT32NE &&
+ params.format != CUBEB_SAMPLE_S16NE) {
return CUBEB_ERROR_INVALID_FORMAT;
}
- IMMDevice * device;
- hr = get_default_endpoint(&device, eRender);
+ ERole role = pref_to_role(params.prefs);
+
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, role);
if (FAILED(hr)) {
- LOG("Could not get default endpoint: %x", hr);
+ LOG("Could not get default endpoint: %lx", hr);
return CUBEB_ERROR;
}
- hr = device->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL, (void **)&client);
- SafeRelease(device);
+ com_ptr<IAudioClient> client;
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ client.receive_vpp());
if (FAILED(hr)) {
- LOG("Could not activate device for latency: %x", hr);
+ LOG("Could not activate device for latency: %lx", hr);
return CUBEB_ERROR;
}
- /* The second parameter is for exclusive mode, that we don't use. */
- hr = client->GetDevicePeriod(&default_period, NULL);
+ REFERENCE_TIME minimum_period;
+ REFERENCE_TIME default_period;
+ hr = client->GetDevicePeriod(&default_period, &minimum_period);
if (FAILED(hr)) {
- SafeRelease(client);
- LOG("Could not get device period: %x", hr);
+ LOG("Could not get device period: %lx", hr);
return CUBEB_ERROR;
}
- LOG("default device period: %lld", default_period);
+ LOG("default device period: %I64d, minimum device period: %I64d",
+ default_period, minimum_period);
- /* According to the docs, the best latency we can achieve is by synchronizing
- the stream and the engine.
- http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */
+ /* If we're on Windows 10, we can use IAudioClient3 to get minimal latency.
+ Otherwise, according to the docs, the best latency we can achieve is by
+ synchronizing the stream and the engine.
+ http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx
+ */
+ // #ifdef _WIN32_WINNT_WIN10
+#if 0
+ *latency_frames = hns_to_frames(params.rate, minimum_period);
+#else
*latency_frames = hns_to_frames(params.rate, default_period);
+#endif
LOG("Minimum latency in frames: %u", *latency_frames);
- SafeRelease(client);
-
return CUBEB_OK;
}
int
wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
{
- HRESULT hr;
- IAudioClient * client;
- WAVEFORMATEX * mix_format;
- auto_com com;
- if (!com.ok()) {
- return CUBEB_ERROR;
- }
-
- IMMDevice * device;
- hr = get_default_endpoint(&device, eRender);
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, eConsole);
if (FAILED(hr)) {
return CUBEB_ERROR;
}
- hr = device->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL, (void **)&client);
- SafeRelease(device);
+ com_ptr<IAudioClient> client;
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ client.receive_vpp());
if (FAILED(hr)) {
return CUBEB_ERROR;
}
- hr = client->GetMixFormat(&mix_format);
+ WAVEFORMATEX * tmp = nullptr;
+ hr = client->GetMixFormat(&tmp);
if (FAILED(hr)) {
- SafeRelease(client);
return CUBEB_ERROR;
}
+ com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
*rate = mix_format->nSamplesPerSec;
LOG("Preferred sample rate for output: %u", *rate);
- CoTaskMemFree(mix_format);
- SafeRelease(client);
-
return CUBEB_OK;
}
-void wasapi_stream_destroy(cubeb_stream * stm);
+void
+wasapi_stream_destroy(cubeb_stream * stm);
-/* Based on the mix format and the stream format, try to find a way to play
- what the user requested. */
static void
-handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params)
+waveformatex_update_derived_properties(WAVEFORMATEX * format)
{
- /* Common case: the hardware is stereo. Up-mixing and down-mixing will be
- handled in the callback. */
- if ((*mix_format)->nChannels <= 2) {
- return;
+ format->nBlockAlign = format->wBitsPerSample * format->nChannels / 8;
+ format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
+ if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ WAVEFORMATEXTENSIBLE * format_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(format);
+ format_pcm->Samples.wValidBitsPerSample = format->wBitsPerSample;
}
+}
+/* Based on the mix format and the stream format, try to find a way to play
+ what the user requested. */
+static void
+handle_channel_layout(cubeb_stream * stm, EDataFlow direction,
+ com_heap_ptr<WAVEFORMATEX> & mix_format,
+ const cubeb_stream_params * stream_params)
+{
+ com_ptr<IAudioClient> & audio_client =
+ (direction == eRender) ? stm->output_client : stm->input_client;
+ XASSERT(audio_client);
/* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
so the reinterpret_cast below should be safe. In practice, this is not
true, and we just want to bail out and let the rest of the code find a good
conversion path instead of trying to make WASAPI do it by itself.
- [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
- if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
+ [1]:
+ http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
+ if (mix_format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
return;
}
- WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(*mix_format);
+ WAVEFORMATEXTENSIBLE * format_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
- /* Stash a copy of the original mix format in case we need to restore it later. */
+ /* Stash a copy of the original mix format in case we need to restore it
+ * later. */
WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm;
- /* The hardware is in surround mode, we want to only use front left and front
- right. Try that, and check if it works. */
- switch (stream_params->channels) {
- case 1: /* Mono */
- format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO;
- break;
- case 2: /* Stereo */
- format_pcm->dwChannelMask = KSAUDIO_SPEAKER_STEREO;
- break;
- default:
- XASSERT(false && "Channel layout not supported.");
- break;
- }
- (*mix_format)->nChannels = stream_params->channels;
- (*mix_format)->nBlockAlign = ((*mix_format)->wBitsPerSample * (*mix_format)->nChannels) / 8;
- (*mix_format)->nAvgBytesPerSec = (*mix_format)->nSamplesPerSec * (*mix_format)->nBlockAlign;
- format_pcm->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- (*mix_format)->wBitsPerSample = 32;
- format_pcm->Samples.wValidBitsPerSample = (*mix_format)->wBitsPerSample;
+ /* Get the channel mask by the channel layout.
+ If the layout is not supported, we will get a closest settings below. */
+ format_pcm->dwChannelMask = stream_params->layout;
+ mix_format->nChannels = stream_params->channels;
+ waveformatex_update_derived_properties(mix_format.get());
/* Check if wasapi will accept our channel layout request. */
- WAVEFORMATEX * closest;
- HRESULT hr = stm->output_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
- *mix_format,
- &closest);
+ WAVEFORMATEX * tmp = nullptr;
+ HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
+ mix_format.get(), &tmp);
+ com_heap_ptr<WAVEFORMATEX> closest(tmp);
if (hr == S_FALSE) {
- /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the
- eventual upmix/downmix ourselves */
+ /* Channel layout not supported, but WASAPI gives us a suggestion. Use it,
+ and handle the eventual upmix/downmix ourselves. Ignore the subformat of
+ the suggestion, since it seems to always be IEEE_FLOAT. */
LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
- WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest);
- XASSERT(closest_pcm->SubFormat == format_pcm->SubFormat);
- CoTaskMemFree(*mix_format);
- *mix_format = closest;
+ XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
+ WAVEFORMATEXTENSIBLE * closest_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest.get());
+ format_pcm->dwChannelMask = closest_pcm->dwChannelMask;
+ mix_format->nChannels = closest->nChannels;
+ waveformatex_update_derived_properties(mix_format.get());
} else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
/* Not supported, no suggestion. This should not happen, but it does in the
field with some sound cards. We restore the mix format, and let the rest
of the code figure out the right conversion path. */
- *reinterpret_cast<WAVEFORMATEXTENSIBLE *>(*mix_format) = hw_mix_format;
+ XASSERT(mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
+ *reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get()) = hw_mix_format;
} else if (hr == S_OK) {
LOG("Requested format accepted by WASAPI.");
} else {
- LOG("IsFormatSupported unhandled error: %x", hr);
+ LOG("IsFormatSupported unhandled error: %lx", hr);
+ }
+}
+
+static bool
+initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client)
+{
+ com_ptr<IAudioClient2> audio_client2;
+ audio_client->QueryInterface<IAudioClient2>(audio_client2.receive());
+ if (!audio_client2) {
+ LOG("Could not get IAudioClient2 interface, not setting "
+ "AUDCLNT_STREAMOPTIONS_RAW.");
+ return CUBEB_OK;
+ }
+ AudioClientProperties properties = {0};
+ properties.cbSize = sizeof(AudioClientProperties);
+#ifndef __MINGW32__
+ properties.Options |= AUDCLNT_STREAMOPTIONS_RAW;
+#endif
+ HRESULT hr = audio_client2->SetClientProperties(&properties);
+ if (FAILED(hr)) {
+ LOG("IAudioClient2::SetClientProperties error: %lx", GetLastError());
+ return CUBEB_ERROR;
}
+ return CUBEB_OK;
+}
+
+// Not static to suppress a warning.
+/* static */ bool
+initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
+ cubeb_stream * stm,
+ const com_heap_ptr<WAVEFORMATEX> & mix_format,
+ DWORD flags, EDataFlow direction)
+{
+ com_ptr<IAudioClient3> audio_client3;
+ audio_client->QueryInterface<IAudioClient3>(audio_client3.receive());
+ if (!audio_client3) {
+ LOG("Could not get IAudioClient3 interface");
+ return false;
+ }
+
+ if (flags & AUDCLNT_STREAMFLAGS_LOOPBACK) {
+ // IAudioClient3 doesn't work with loopback streams, and will return error
+ // 88890021: AUDCLNT_E_INVALID_STREAM_FLAG
+ LOG("Audio stream is loopback, not using IAudioClient3");
+ return false;
+ }
+
+ // Some people have reported glitches with capture streams:
+ // http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html
+ if (direction == eCapture) {
+ LOG("Audio stream is capture, not using IAudioClient3");
+ return false;
+ }
+
+ // Possibly initialize a shared-mode stream using IAudioClient3. Initializing
+ // a stream this way lets you request lower latencies, but also locks the
+ // global WASAPI engine at that latency.
+ // - If we request a shared-mode stream, streams created with IAudioClient
+ // will
+ // have their latency adjusted to match. When the shared-mode stream is
+ // closed, they'll go back to normal.
+ // - If there's already a shared-mode stream running, then we cannot request
+ // the engine change to a different latency - we have to match it.
+ // - It's antisocial to lock the WASAPI engine at its default latency. If we
+ // would do this, then stop and use IAudioClient instead.
+
+ HRESULT hr;
+ uint32_t default_period = 0, fundamental_period = 0, min_period = 0,
+ max_period = 0;
+ hr = audio_client3->GetSharedModeEnginePeriod(
+ mix_format.get(), &default_period, &fundamental_period, &min_period,
+ &max_period);
+ if (FAILED(hr)) {
+ LOG("Could not get shared mode engine period: error: %lx", hr);
+ return false;
+ }
+ uint32_t requested_latency = stm->latency;
+ if (requested_latency >= default_period) {
+ LOG("Requested latency %i greater than default latency %i, not using "
+ "IAudioClient3",
+ requested_latency, default_period);
+ return false;
+ }
+ LOG("Got shared mode engine period: default=%i fundamental=%i min=%i max=%i",
+ default_period, fundamental_period, min_period, max_period);
+ // Snap requested latency to a valid value
+ uint32_t old_requested_latency = requested_latency;
+ if (requested_latency < min_period) {
+ requested_latency = min_period;
+ }
+ requested_latency -= (requested_latency - min_period) % fundamental_period;
+ if (requested_latency != old_requested_latency) {
+ LOG("Requested latency %i was adjusted to %i", old_requested_latency,
+ requested_latency);
+ }
+
+ hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency,
+ mix_format.get(), NULL);
+ if (SUCCEEDED(hr)) {
+ return true;
+ } else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) {
+ LOG("Got AUDCLNT_E_ENGINE_PERIODICITY_LOCKED, adjusting latency request");
+ } else {
+ LOG("Could not initialize shared stream with IAudioClient3: error: %lx",
+ hr);
+ return false;
+ }
+
+ uint32_t current_period = 0;
+ WAVEFORMATEX * current_format = nullptr;
+ // We have to pass a valid WAVEFORMATEX** and not nullptr, otherwise
+ // GetCurrentSharedModeEnginePeriod will return E_POINTER
+ hr = audio_client3->GetCurrentSharedModeEnginePeriod(&current_format,
+ &current_period);
+ CoTaskMemFree(current_format);
+ if (FAILED(hr)) {
+ LOG("Could not get current shared mode engine period: error: %lx", hr);
+ return false;
+ }
+
+ if (current_period >= default_period) {
+ LOG("Current shared mode engine period %i too high, not using IAudioClient",
+ current_period);
+ return false;
+ }
+
+ hr = audio_client3->InitializeSharedAudioStream(flags, current_period,
+ mix_format.get(), NULL);
+ if (SUCCEEDED(hr)) {
+ LOG("Current shared mode engine period is %i instead of requested %i",
+ current_period, requested_latency);
+ return true;
+ }
+
+ LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr);
+ return false;
}
#define DIRECTION_NAME (direction == eCapture ? "capture" : "render")
-template<typename T>
-int setup_wasapi_stream_one_side(cubeb_stream * stm,
- cubeb_stream_params * stream_params,
- cubeb_devid devid,
- EDataFlow direction,
- REFIID riid,
- IAudioClient ** audio_client,
- uint32_t * buffer_frame_count,
- HANDLE & event,
- T ** render_or_capture_client,
- cubeb_stream_params * mix_params)
+template <typename T>
+int
+setup_wasapi_stream_one_side(cubeb_stream * stm,
+ cubeb_stream_params * stream_params,
+ wchar_t const * devid, EDataFlow direction,
+ REFIID riid, com_ptr<IAudioClient> & audio_client,
+ uint32_t * buffer_frame_count, HANDLE & event,
+ T & render_or_capture_client,
+ cubeb_stream_params * mix_params,
+ com_ptr<IMMDevice> & device)
{
- IMMDevice * device;
- WAVEFORMATEX * mix_format;
HRESULT hr;
+ bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK;
+ if (is_loopback && direction != eCapture) {
+ LOG("Loopback pref can only be used with capture streams!\n");
+ return CUBEB_ERROR;
+ }
stm->stream_reset_lock.assert_current_thread_owns();
bool try_again = false;
@@ -1455,35 +2048,54 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
// possibilities.
do {
if (devid) {
- std::unique_ptr<const wchar_t[]> id(utf8_to_wstr(reinterpret_cast<char*>(devid)));
- hr = get_endpoint(&device, id.get());
+ hr = get_endpoint(device, devid);
if (FAILED(hr)) {
- LOG("Could not get %s endpoint, error: %x\n", DIRECTION_NAME, hr);
+ LOG("Could not get %s endpoint, error: %lx\n", DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
- }
- else {
- hr = get_default_endpoint(&device, direction);
+ } else {
+ // If caller has requested loopback but not specified a device, look for
+ // the default render device. Otherwise look for the default device
+ // appropriate to the direction.
+ hr = get_default_endpoint(device, is_loopback ? eRender : direction,
+ pref_to_role(stream_params->prefs));
if (FAILED(hr)) {
- LOG("Could not get default %s endpoint, error: %x\n", DIRECTION_NAME, hr);
+ if (is_loopback) {
+ LOG("Could not get default render endpoint for loopback, error: "
+ "%lx\n",
+ hr);
+ } else {
+ LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME,
+ hr);
+ }
return CUBEB_ERROR;
}
}
/* Get a client. We will get all other interfaces we need from
* this pointer. */
- hr = device->Activate(__uuidof(IAudioClient),
+#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
+ hr = device->Activate(__uuidof(IAudioClient3),
CLSCTX_INPROC_SERVER,
- NULL, (void **)audio_client);
- SafeRelease(device);
+ NULL, audio_client.receive_vpp());
+ if (hr == E_NOINTERFACE) {
+#endif
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ audio_client.receive_vpp());
+#if 0
+ }
+#endif
+
if (FAILED(hr)) {
LOG("Could not activate the device to get an audio"
- " client for %s: error: %x\n", DIRECTION_NAME, hr);
+ " client for %s: error: %lx\n",
+ DIRECTION_NAME, hr);
// A particular device can't be activated because it has been
// unplugged, try fall back to the default audio device.
if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) {
LOG("Trying again with the default %s audio device.", DIRECTION_NAME);
devid = nullptr;
+ device = nullptr;
try_again = true;
} else {
return CUBEB_ERROR;
@@ -1495,62 +2107,154 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
/* We have to distinguish between the format the mixer uses,
* and the format the stream we want to play uses. */
- hr = (*audio_client)->GetMixFormat(&mix_format);
+ WAVEFORMATEX * tmp = nullptr;
+ hr = audio_client->GetMixFormat(&tmp);
if (FAILED(hr)) {
LOG("Could not fetch current mix format from the audio"
- " client for %s: error: %x", DIRECTION_NAME, hr);
+ " client for %s: error: %lx",
+ DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
+ com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
+
+ mix_format->wBitsPerSample = stm->bytes_per_sample * 8;
+ if (mix_format->wFormatTag == WAVE_FORMAT_PCM ||
+ mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
+ switch (mix_format->wBitsPerSample) {
+ case 8:
+ case 16:
+ mix_format->wFormatTag = WAVE_FORMAT_PCM;
+ break;
+ case 32:
+ mix_format->wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+ break;
+ default:
+ LOG("%u bits per sample is incompatible with PCM wave formats",
+ mix_format->wBitsPerSample);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ WAVEFORMATEXTENSIBLE * format_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
+ format_pcm->SubFormat = stm->waveformatextensible_sub_format;
+ }
+ waveformatex_update_derived_properties(mix_format.get());
- handle_channel_layout(stm, &mix_format, stream_params);
+ /* Set channel layout only when there're more than two channels. Otherwise,
+ * use the default setting retrieved from the stream format of the audio
+ * engine's internal processing by GetMixFormat. */
+ if (mix_format->nChannels > 2) {
+ handle_channel_layout(stm, direction, mix_format, stream_params);
+ }
- /* Shared mode WASAPI always supports float32 sample format, so this
- * is safe. */
- mix_params->format = CUBEB_SAMPLE_FLOAT32NE;
+ mix_params->format = stream_params->format;
mix_params->rate = mix_format->nSamplesPerSec;
mix_params->channels = mix_format->nChannels;
- LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]",
+ mix_params->layout = mask_to_channel_layout(mix_format.get());
+
+ LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]",
stream_params->format, stream_params->rate, stream_params->channels,
- mix_params->format, mix_params->rate, mix_params->channels);
-
- hr = (*audio_client)->Initialize(AUDCLNT_SHAREMODE_SHARED,
- AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
- AUDCLNT_STREAMFLAGS_NOPERSIST,
- frames_to_hns(stm, stm->latency),
- 0,
- mix_format,
- NULL);
+ stream_params->layout, mix_params->format, mix_params->rate,
+ mix_params->channels, mix_params->layout);
+
+ DWORD flags = 0;
+
+ // Check if a loopback device should be requested. Note that event callbacks
+ // do not work with loopback devices, so only request these if not looping.
+ if (is_loopback) {
+ flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
+ } else {
+ flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+ }
+
+ // Sanity check the latency, it may be that the device doesn't support it.
+ REFERENCE_TIME minimum_period;
+ REFERENCE_TIME default_period;
+ hr = audio_client->GetDevicePeriod(&default_period, &minimum_period);
if (FAILED(hr)) {
- LOG("Unable to initialize audio client for %s: %x.", DIRECTION_NAME, hr);
+ LOG("Could not get device period: %lx", hr);
return CUBEB_ERROR;
}
- CoTaskMemFree(mix_format);
+ REFERENCE_TIME latency_hns;
- hr = (*audio_client)->GetBufferSize(buffer_frame_count);
- if (FAILED(hr)) {
- LOG("Could not get the buffer size from the client"
- " for %s %x.", DIRECTION_NAME, hr);
- return CUBEB_ERROR;
+ uint32_t latency_frames = stm->latency;
+ cubeb_device_info device_info;
+ int rv = wasapi_create_device(stm->context, device_info,
+ stm->device_enumerator.get(), device.get());
+ if (rv == CUBEB_OK) {
+ const char * HANDSFREE_TAG = "BTHHFENUM";
+ size_t len = sizeof(HANDSFREE_TAG);
+ if (direction == eCapture) {
+ uint32_t default_period_frames =
+ hns_to_frames(device_info.default_rate, default_period);
+ if (strlen(device_info.group_id) >= len &&
+ strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) {
+ stm->input_bluetooth_handsfree = true;
+ } else {
+ stm->input_bluetooth_handsfree = false;
+ }
+ // This multiplicator has been found empirically.
+ latency_frames = default_period_frames * 8;
+ LOG("Input: latency increased to %u frames from a default of %u",
+ latency_frames, default_period_frames);
+ }
+ latency_hns = frames_to_hns(device_info.default_rate, latency_frames);
+
+ wasapi_destroy_device(&device_info);
+ } else {
+ stm->input_bluetooth_handsfree = false;
+ latency_hns = frames_to_hns(mix_params->rate, latency_frames);
+ LOG("Could not get cubeb_device_info.");
}
- // Input is up/down mixed when depacketized in get_input_buffer.
- if (has_output(stm) &&
- (should_upmix(*stream_params, *mix_params) ||
- should_downmix(*stream_params, *mix_params))) {
- stm->mix_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, *buffer_frame_count));
+ if (stream_params->prefs & CUBEB_STREAM_PREF_RAW) {
+ if (initialize_iaudioclient2(audio_client) != CUBEB_OK) {
+ LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError());
+ // This is not fatal.
+ }
+ }
+
+#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
+ if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) {
+ LOG("Initialized with IAudioClient3");
+ } else {
+#endif
+ hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, latency_hns, 0,
+ mix_format.get(), NULL);
+#if 0
+ }
+#endif
+ if (FAILED(hr)) {
+ LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
}
- hr = (*audio_client)->SetEventHandle(event);
+ hr = audio_client->GetBufferSize(buffer_frame_count);
if (FAILED(hr)) {
- LOG("Could set the event handle for the %s client %x.",
+ LOG("Could not get the buffer size from the client"
+ " for %s %lx.",
DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
- hr = (*audio_client)->GetService(riid, (void **)render_or_capture_client);
+ LOG("Buffer size is: %d for %s\n", *buffer_frame_count, DIRECTION_NAME);
+
+ // Events are used if not looping back
+ if (!is_loopback) {
+ hr = audio_client->SetEventHandle(event);
+ if (FAILED(hr)) {
+ LOG("Could set the event handle for the %s client %lx.", DIRECTION_NAME,
+ hr);
+ return CUBEB_ERROR;
+ }
+ }
+
+ hr = audio_client->GetService(riid, render_or_capture_client.receive_vpp());
if (FAILED(hr)) {
- LOG("Could not get the %s client %x.", DIRECTION_NAME, hr);
+ LOG("Could not get the %s client %lx.", DIRECTION_NAME, hr);
return CUBEB_ERROR;
}
@@ -1559,83 +2263,175 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
#undef DIRECTION_NAME
-int setup_wasapi_stream(cubeb_stream * stm)
+void
+wasapi_find_matching_output_device(cubeb_stream * stm)
{
HRESULT hr;
- int rv;
+ cubeb_device_info * input_device = nullptr;
+ cubeb_device_collection collection;
- stm->stream_reset_lock.assert_current_thread_owns();
+ // Only try to match to an output device if the input device is a bluetooth
+ // device that is using the handsfree protocol
+ if (!stm->input_bluetooth_handsfree) {
+ return;
+ }
- auto_com com;
- if (!com.ok()) {
- LOG("Failure to initialize COM.");
- return CUBEB_ERROR;
+ wchar_t * tmp = nullptr;
+ hr = stm->input_device->GetId(&tmp);
+ if (FAILED(hr)) {
+ LOG("Couldn't get input device id in wasapi_find_matching_output_device");
+ return;
+ }
+ com_heap_ptr<wchar_t> device_id(tmp);
+ cubeb_devid input_device_id = intern_device_id(stm->context, device_id.get());
+ if (!input_device_id) {
+ return;
}
- XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first.");
+ int rv = wasapi_enumerate_devices(
+ stm->context,
+ (cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT),
+ &collection);
+ if (rv != CUBEB_OK) {
+ return;
+ }
+
+ // Find the input device, and then find the output device with the same group
+ // id and the same rate.
+ for (uint32_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].devid == input_device_id) {
+ input_device = &collection.device[i];
+ break;
+ }
+ }
+
+ for (uint32_t i = 0; i < collection.count; i++) {
+ cubeb_device_info & dev = collection.device[i];
+ if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT && dev.group_id && input_device &&
+ !strcmp(dev.group_id, input_device->group_id) &&
+ dev.default_rate == input_device->default_rate) {
+ LOG("Found matching device for %s: %s", input_device->friendly_name,
+ dev.friendly_name);
+ stm->output_device_id =
+ utf8_to_wstr(reinterpret_cast<char const *>(dev.devid));
+ }
+ }
+
+ wasapi_device_collection_destroy(stm->context, &collection);
+}
+
+int
+setup_wasapi_stream(cubeb_stream * stm)
+{
+ int rv;
+
+ stm->stream_reset_lock.assert_current_thread_owns();
+
+ XASSERT((!stm->output_client || !stm->input_client) &&
+ "WASAPI stream already setup, close it first.");
if (has_input(stm)) {
- LOG("Setup capture: device=%x", (int)stm->input_device);
- rv = setup_wasapi_stream_one_side(stm,
- &stm->input_stream_params,
- stm->input_device,
- eCapture,
- __uuidof(IAudioCaptureClient),
- &stm->input_client,
- &stm->input_buffer_frame_count,
- stm->input_available_event,
- &stm->capture_client,
- &stm->input_mix_params);
+ LOG("(%p) Setup capture: device=%p", stm, stm->input_device_id.get());
+ rv = setup_wasapi_stream_one_side(
+ stm, &stm->input_stream_params, stm->input_device_id.get(), eCapture,
+ __uuidof(IAudioCaptureClient), stm->input_client,
+ &stm->input_buffer_frame_count, stm->input_available_event,
+ stm->capture_client, &stm->input_mix_params, stm->input_device);
if (rv != CUBEB_OK) {
LOG("Failure to open the input side.");
return rv;
}
+
+ // We initializing an input stream, buffer ahead two buffers worth of
+ // silence. This delays the input side slightly, but allow to not glitch
+ // when no input is available when calling into the resampler to call the
+ // callback: the input refill event will be set shortly after to compensate
+ // for this lack of data. In debug, four buffers are used, to avoid tripping
+ // up assertions down the line.
+#if !defined(DEBUG)
+ const int silent_buffer_count = 2;
+#else
+ const int silent_buffer_count = 6;
+#endif
+ stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count *
+ stm->input_stream_params.channels *
+ silent_buffer_count);
+
+ // If this is a bluetooth device, and the output device is the default
+ // device, and the default device is the same bluetooth device, pick the
+ // right output device, running at the same rate and with the same protocol
+ // as the input.
+ if (!stm->output_device_id) {
+ wasapi_find_matching_output_device(stm);
+ }
+ }
+
+ // If we don't have an output device but are requesting a loopback device,
+ // we attempt to open that same device in output mode in order to drive the
+ // loopback via the output events.
+ stm->has_dummy_output = false;
+ if (!has_output(stm) &&
+ stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ stm->output_stream_params.rate = stm->input_stream_params.rate;
+ stm->output_stream_params.channels = stm->input_stream_params.channels;
+ stm->output_stream_params.layout = stm->input_stream_params.layout;
+ if (stm->input_device_id) {
+ size_t len = wcslen(stm->input_device_id.get());
+ std::unique_ptr<wchar_t[]> tmp(new wchar_t[len + 1]);
+ if (wcsncpy_s(tmp.get(), len + 1, stm->input_device_id.get(), len) != 0) {
+ LOG("Failed to copy device identifier while copying input stream"
+ " configuration to output stream configuration to drive loopback.");
+ return CUBEB_ERROR;
+ }
+ stm->output_device_id = move(tmp);
+ }
+ stm->has_dummy_output = true;
}
if (has_output(stm)) {
- LOG("Setup render: device=%x", (int)stm->output_device);
- rv = setup_wasapi_stream_one_side(stm,
- &stm->output_stream_params,
- stm->output_device,
- eRender,
- __uuidof(IAudioRenderClient),
- &stm->output_client,
- &stm->output_buffer_frame_count,
- stm->refill_event,
- &stm->render_client,
- &stm->output_mix_params);
+ LOG("(%p) Setup render: device=%p", stm, stm->output_device_id.get());
+ rv = setup_wasapi_stream_one_side(
+ stm, &stm->output_stream_params, stm->output_device_id.get(), eRender,
+ __uuidof(IAudioRenderClient), stm->output_client,
+ &stm->output_buffer_frame_count, stm->refill_event, stm->render_client,
+ &stm->output_mix_params, stm->output_device);
if (rv != CUBEB_OK) {
LOG("Failure to open the output side.");
return rv;
}
+ HRESULT hr = 0;
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume),
- (void **)&stm->audio_stream_volume);
+ stm->audio_stream_volume.receive_vpp());
if (FAILED(hr)) {
- LOG("Could not get the IAudioStreamVolume: %x", hr);
+ LOG("Could not get the IAudioStreamVolume: %lx", hr);
return CUBEB_ERROR;
}
+#endif
XASSERT(stm->frames_written == 0);
hr = stm->output_client->GetService(__uuidof(IAudioClock),
- (void **)&stm->audio_clock);
+ stm->audio_clock.receive_vpp());
if (FAILED(hr)) {
- LOG("Could not get the IAudioClock: %x", hr);
+ LOG("Could not get the IAudioClock: %lx", hr);
return CUBEB_ERROR;
}
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
/* Restore the stream volume over a device change. */
if (stream_set_volume(stm, stm->volume) != CUBEB_OK) {
LOG("Could not set the volume.");
return CUBEB_ERROR;
}
+#endif
}
/* If we have both input and output, we resample to
* the highest sample rate available. */
int32_t target_sample_rate;
if (has_input(stm) && has_output(stm)) {
- assert(stm->input_stream_params.rate == stm->output_stream_params.rate);
+ XASSERT(stm->input_stream_params.rate == stm->output_stream_params.rate);
target_sample_rate = stm->input_stream_params.rate;
} else if (has_input(stm)) {
target_sample_rate = stm->input_stream_params.rate;
@@ -1655,14 +2451,12 @@ int setup_wasapi_stream(cubeb_stream * stm)
cubeb_stream_params output_params = stm->output_mix_params;
output_params.channels = stm->output_stream_params.channels;
- stm->resampler =
- cubeb_resampler_create(stm,
- has_input(stm) ? &input_params : nullptr,
- has_output(stm) ? &output_params : nullptr,
- target_sample_rate,
- stm->data_callback,
- stm->user_ptr,
- CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ stm->resampler.reset(cubeb_resampler_create(
+ stm, has_input(stm) ? &input_params : nullptr,
+ has_output(stm) ? &output_params : nullptr, target_sample_rate,
+ stm->data_callback, stm->user_ptr,
+ stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP
+ : CUBEB_RESAMPLER_QUALITY_DESKTOP));
if (!stm->resampler) {
LOG("Could not get a resampler");
return CUBEB_ERROR;
@@ -1678,189 +2472,264 @@ int setup_wasapi_stream(cubeb_stream * stm)
stm->refill_callback = refill_callback_output;
}
+ // Create input mixer.
+ if (has_input(stm) &&
+ ((stm->input_mix_params.layout != CUBEB_LAYOUT_UNDEFINED &&
+ stm->input_mix_params.layout != stm->input_stream_params.layout) ||
+ (stm->input_mix_params.channels != stm->input_stream_params.channels))) {
+ if (stm->input_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) {
+ LOG("Input stream using undefined layout! Any mixing may be "
+ "unpredictable!\n");
+ }
+ stm->input_mixer.reset(cubeb_mixer_create(
+ stm->input_stream_params.format, stm->input_mix_params.channels,
+ stm->input_mix_params.layout, stm->input_stream_params.channels,
+ stm->input_stream_params.layout));
+ assert(stm->input_mixer);
+ }
+
+ // Create output mixer.
+ if (has_output(stm) &&
+ stm->output_mix_params.layout != stm->output_stream_params.layout) {
+ if (stm->output_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) {
+ LOG("Output stream using undefined layout! Any mixing may be "
+ "unpredictable!\n");
+ }
+ stm->output_mixer.reset(cubeb_mixer_create(
+ stm->output_stream_params.format, stm->output_stream_params.channels,
+ stm->output_stream_params.layout, stm->output_mix_params.channels,
+ stm->output_mix_params.layout));
+ assert(stm->output_mixer);
+ // Input is up/down mixed when depacketized in get_input_buffer.
+ stm->mix_buffer.resize(
+ frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count));
+ }
+
return CUBEB_OK;
}
+ERole
+pref_to_role(cubeb_stream_prefs prefs)
+{
+ if (prefs & CUBEB_STREAM_PREF_VOICE) {
+ return eCommunications;
+ }
+
+ return eConsole;
+}
+
int
wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
- char const * stream_name,
- cubeb_devid input_device,
+ char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
- unsigned int latency_frames, cubeb_data_callback data_callback,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
cubeb_state_callback state_callback, void * user_ptr)
{
- HRESULT hr;
int rv;
- auto_com com;
- if (!com.ok()) {
- return CUBEB_ERROR;
- }
XASSERT(context && stream && (input_stream_params || output_stream_params));
- if (output_stream_params && output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE ||
- input_stream_params && input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE) {
- LOG("Invalid format, %p %p %d %d",
- output_stream_params, input_stream_params,
- output_stream_params && output_stream_params->format,
- input_stream_params && input_stream_params->format);
+ if (output_stream_params && input_stream_params &&
+ output_stream_params->format != input_stream_params->format) {
return CUBEB_ERROR_INVALID_FORMAT;
}
- cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
-
- XASSERT(stm);
+ std::unique_ptr<cubeb_stream, decltype(&wasapi_stream_destroy)> stm(
+ new cubeb_stream(), wasapi_stream_destroy);
stm->context = context;
stm->data_callback = data_callback;
stm->state_callback = state_callback;
stm->user_ptr = user_ptr;
- stm->draining = false;
+ stm->role = eConsole;
+ stm->input_bluetooth_handsfree = false;
+
+ HRESULT hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(stm->device_enumerator.receive()));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %lx", hr);
+ return hr;
+ }
+
if (input_stream_params) {
stm->input_stream_params = *input_stream_params;
- stm->input_device = input_device;
+ stm->input_device_id =
+ utf8_to_wstr(reinterpret_cast<char const *>(input_device));
}
if (output_stream_params) {
stm->output_stream_params = *output_stream_params;
- stm->output_device = output_device;
+ stm->output_device_id =
+ utf8_to_wstr(reinterpret_cast<char const *>(output_device));
}
- stm->latency = latency_frames;
- stm->volume = 1.0;
+ if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE ||
+ stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) {
+ stm->voice = true;
+ } else {
+ stm->voice = false;
+ }
+
+ switch (output_stream_params ? output_stream_params->format
+ : input_stream_params->format) {
+ case CUBEB_SAMPLE_S16NE:
+ stm->bytes_per_sample = sizeof(short);
+ stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM;
+ stm->linear_input_buffer.reset(new auto_array_wrapper_impl<short>);
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ stm->bytes_per_sample = sizeof(float);
+ stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ stm->linear_input_buffer.reset(new auto_array_wrapper_impl<float>);
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
- // Placement new to call ctor.
- new (&stm->stream_reset_lock) owned_critical_section();
+ stm->latency = latency_frames;
stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->reconfigure_event) {
- LOG("Can't create the reconfigure event, error: %x", GetLastError());
- wasapi_stream_destroy(stm);
+ LOG("Can't create the reconfigure event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
/* Unconditionally create the two events so that the wait logic is simpler. */
stm->refill_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->refill_event) {
- LOG("Can't create the refill event, error: %x", GetLastError());
- wasapi_stream_destroy(stm);
+ LOG("Can't create the refill event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
stm->input_available_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->input_available_event) {
- LOG("Can't create the input available event , error: %x", GetLastError());
- wasapi_stream_destroy(stm);
+ LOG("Can't create the input available event , error: %lx", GetLastError());
return CUBEB_ERROR;
}
-
{
/* Locking here is not strictly necessary, because we don't have a
notification client that can reset the stream yet, but it lets us
assert that the lock is held in the function. */
auto_lock lock(stm->stream_reset_lock);
- rv = setup_wasapi_stream(stm);
+ rv = setup_wasapi_stream(stm.get());
}
if (rv != CUBEB_OK) {
- wasapi_stream_destroy(stm);
return rv;
}
- hr = register_notification_client(stm);
- if (FAILED(hr)) {
- /* this is not fatal, we can still play audio, but we won't be able
- to keep using the default audio endpoint if it changes. */
- LOG("failed to register notification client, %x", hr);
+ if (!((input_stream_params ? (input_stream_params->prefs &
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING)
+ : 0) ||
+ (output_stream_params ? (output_stream_params->prefs &
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING)
+ : 0))) {
+ HRESULT hr = register_notification_client(stm.get());
+ if (FAILED(hr)) {
+ /* this is not fatal, we can still play audio, but we won't be able
+ to keep using the default audio endpoint if it changes. */
+ LOG("failed to register notification client, %lx", hr);
+ }
}
- *stream = stm;
+ *stream = stm.release();
+ LOG("Stream init succesfull (%p)", *stream);
return CUBEB_OK;
}
-void close_wasapi_stream(cubeb_stream * stm)
+void
+close_wasapi_stream(cubeb_stream * stm)
{
XASSERT(stm);
stm->stream_reset_lock.assert_current_thread_owns();
- SafeRelease(stm->output_client);
- stm->output_client = NULL;
- SafeRelease(stm->input_client);
- stm->input_client = NULL;
+ stm->output_client = nullptr;
+ stm->render_client = nullptr;
- SafeRelease(stm->render_client);
- stm->render_client = NULL;
+ stm->input_client = nullptr;
+ stm->capture_client = nullptr;
- SafeRelease(stm->capture_client);
- stm->capture_client = NULL;
+ stm->output_device = nullptr;
+ stm->input_device = nullptr;
- SafeRelease(stm->audio_stream_volume);
- stm->audio_stream_volume = NULL;
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+ stm->audio_stream_volume = nullptr;
+#endif
- SafeRelease(stm->audio_clock);
- stm->audio_clock = NULL;
- stm->total_frames_written += static_cast<UINT64>(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params)));
+ stm->audio_clock = nullptr;
+ stm->total_frames_written += static_cast<UINT64>(
+ round(stm->frames_written *
+ stream_to_mix_samplerate_ratio(stm->output_stream_params,
+ stm->output_mix_params)));
stm->frames_written = 0;
- if (stm->resampler) {
- cubeb_resampler_destroy(stm->resampler);
- stm->resampler = NULL;
+ stm->resampler.reset();
+ stm->output_mixer.reset();
+ stm->input_mixer.reset();
+ stm->mix_buffer.clear();
+ if (stm->linear_input_buffer) {
+ stm->linear_input_buffer->clear();
}
-
- free(stm->mix_buffer);
- stm->mix_buffer = NULL;
}
-void wasapi_stream_destroy(cubeb_stream * stm)
+void
+wasapi_stream_destroy(cubeb_stream * stm)
{
XASSERT(stm);
+ LOG("Stream destroy (%p)", stm);
- // Only free stm->emergency_bailout if we could not join the thread.
- // If we could not join the thread, stm->emergency_bailout is true
+ // Only free stm->emergency_bailout if we could join the thread.
+ // If we could not join the thread, stm->emergency_bailout is true
// and is still alive until the thread wakes up and exits cleanly.
if (stop_and_join_render_thread(stm)) {
delete stm->emergency_bailout.load();
stm->emergency_bailout = nullptr;
}
- unregister_notification_client(stm);
+ if (stm->notification_client) {
+ unregister_notification_client(stm);
+ }
- SafeRelease(stm->reconfigure_event);
- SafeRelease(stm->refill_event);
- SafeRelease(stm->input_available_event);
+ CloseHandle(stm->reconfigure_event);
+ CloseHandle(stm->refill_event);
+ CloseHandle(stm->input_available_event);
+
+ // The variables intialized in wasapi_stream_init,
+ // must be destroyed in wasapi_stream_destroy.
+ stm->linear_input_buffer.reset();
+
+ stm->device_enumerator = nullptr;
{
auto_lock lock(stm->stream_reset_lock);
close_wasapi_stream(stm);
}
- // Need to call dtor to free the resource in owned_critical_section.
- stm->stream_reset_lock.~owned_critical_section();
-
- free(stm);
+ delete stm;
}
-enum StreamDirection {
- OUTPUT,
- INPUT
-};
+enum StreamDirection { OUTPUT, INPUT };
-int stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
+int
+stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
{
XASSERT((dir == OUTPUT && stm->output_client) ||
(dir == INPUT && stm->input_client));
- HRESULT hr = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start();
+ HRESULT hr =
+ dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start();
if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
LOG("audioclient invalidated for %s device, reconfiguring",
dir == OUTPUT ? "output" : "input");
BOOL ok = ResetEvent(stm->reconfigure_event);
if (!ok) {
- LOG("resetting reconfig event failed for %s stream: %x",
+ LOG("resetting reconfig event failed for %s stream: %lx",
dir == OUTPUT ? "output" : "input", GetLastError());
}
@@ -1871,14 +2740,15 @@ int stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
return r;
}
- HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start();
+ HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start()
+ : stm->input_client->Start();
if (FAILED(hr2)) {
- LOG("could not start the %s stream after reconfig: %x",
+ LOG("could not start the %s stream after reconfig: %lx",
dir == OUTPUT ? "output" : "input", hr);
return CUBEB_ERROR;
}
} else if (FAILED(hr)) {
- LOG("could not start the %s stream: %x.",
+ LOG("could not start the %s stream: %lx.",
dir == OUTPUT ? "output" : "input", hr);
return CUBEB_ERROR;
}
@@ -1886,7 +2756,8 @@ int stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
return CUBEB_OK;
}
-int wasapi_stream_start(cubeb_stream * stm)
+int
+wasapi_stream_start(cubeb_stream * stm)
{
auto_lock lock(stm->stream_reset_lock);
@@ -1911,22 +2782,40 @@ int wasapi_stream_start(cubeb_stream * stm)
stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->shutdown_event) {
- LOG("Can't create the shutdown event, error: %x", GetLastError());
+ LOG("Can't create the shutdown event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
- stm->thread = (HANDLE) _beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
+ stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL);
+ if (!stm->thread_ready_event) {
+ LOG("Can't create the thread_ready event, error: %lx", GetLastError());
+ return CUBEB_ERROR;
+ }
+
+ cubeb_async_log_reset_threads();
+ stm->thread =
+ (HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
+ STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
if (stm->thread == NULL) {
LOG("could not create WASAPI render thread.");
return CUBEB_ERROR;
}
+ // Wait for wasapi_stream_render_loop to signal that emergency_bailout has
+ // been read, avoiding a bailout situation where we could free `stm`
+ // before wasapi_stream_render_loop had a chance to run.
+ HRESULT hr = WaitForSingleObject(stm->thread_ready_event, INFINITE);
+ XASSERT(hr == WAIT_OBJECT_0);
+ CloseHandle(stm->thread_ready_event);
+ stm->thread_ready_event = 0;
+
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
return CUBEB_OK;
}
-int wasapi_stream_stop(cubeb_stream * stm)
+int
+wasapi_stream_stop(cubeb_stream * stm)
{
XASSERT(stm);
HRESULT hr;
@@ -1950,22 +2839,23 @@ int wasapi_stream_stop(cubeb_stream * stm)
}
}
-
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
}
if (stop_and_join_render_thread(stm)) {
- // This is null if we've given the pointer to the other thread
- if (stm->emergency_bailout.load()) {
- delete stm->emergency_bailout.load();
- stm->emergency_bailout = nullptr;
- }
+ delete stm->emergency_bailout.load();
+ stm->emergency_bailout = nullptr;
+ } else {
+ // If we could not join the thread, put the stream in error.
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return CUBEB_ERROR;
}
return CUBEB_OK;
}
-int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
+int
+wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
{
XASSERT(stm && position);
auto_lock lock(stm->stream_reset_lock);
@@ -1975,11 +2865,16 @@ int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
}
/* Calculate how far behind the current stream head the playback cursor is. */
- uint64_t stream_delay = static_cast<uint64_t>(current_stream_delay(stm) * stm->output_stream_params.rate);
+ uint64_t stream_delay = static_cast<uint64_t>(current_stream_delay(stm) *
+ stm->output_stream_params.rate);
/* Calculate the logical stream head in frames at the stream sample rate. */
- uint64_t max_pos = stm->total_frames_written +
- static_cast<uint64_t>(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params)));
+ uint64_t max_pos =
+ stm->total_frames_written +
+ static_cast<uint64_t>(
+ round(stm->frames_written *
+ stream_to_mix_samplerate_ratio(stm->output_stream_params,
+ stm->output_mix_params)));
*position = max_pos;
if (stream_delay <= *position) {
@@ -1994,7 +2889,8 @@ int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
return CUBEB_OK;
}
-int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+int
+wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
XASSERT(stm && latency);
@@ -2007,20 +2903,55 @@ int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
/* The GetStreamLatency method only works if the
AudioClient has been initialized. */
if (!stm->output_client) {
+ LOG("get_latency: No output_client.");
return CUBEB_ERROR;
}
REFERENCE_TIME latency_hns;
HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns);
if (FAILED(hr)) {
+ LOG("GetStreamLatency failed %lx.", hr);
return CUBEB_ERROR;
}
- *latency = hns_to_frames(stm, latency_hns);
+ // This happens on windows 10: no error, but always 0 for latency.
+ if (latency_hns == 0) {
+ LOG("GetStreamLatency returned 0, using workaround.");
+ double delay_s = current_stream_delay(stm);
+ // convert to sample-frames
+ *latency = delay_s * stm->output_stream_params.rate;
+ } else {
+ *latency = hns_to_frames(stm, latency_hns);
+ }
+
+ LOG("Output latency %u frames.", *latency);
return CUBEB_OK;
}
-int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
+int
+wasapi_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ XASSERT(stm && latency);
+
+ if (!has_input(stm)) {
+ LOG("Input latency queried on an output-only stream.");
+ return CUBEB_ERROR;
+ }
+
+ auto_lock lock(stm->stream_reset_lock);
+
+ if (stm->input_latency_hns == LATENCY_NOT_AVAILABLE_YET) {
+ LOG("Input latency not available yet.");
+ return CUBEB_ERROR;
+ }
+
+ *latency = hns_to_frames(stm, stm->input_latency_hns);
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_stream_set_volume(cubeb_stream * stm, float volume)
{
auto_lock lock(stm->stream_reset_lock);
@@ -2028,284 +2959,435 @@ int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_ERROR;
}
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
if (stream_set_volume(stm, volume) != CUBEB_OK) {
return CUBEB_ERROR;
}
+#endif
stm->volume = volume;
return CUBEB_OK;
}
-static char *
+static char const *
wstr_to_utf8(LPCWSTR str)
{
- char * ret = NULL;
- int size;
-
- size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, 0, NULL, NULL);
- if (size > 0) {
- ret = static_cast<char *>(malloc(size));
- ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL);
+ int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL);
+ if (size <= 0) {
+ return nullptr;
}
+ char * ret = static_cast<char *>(malloc(size));
+ ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL);
return ret;
}
-static std::unique_ptr<const wchar_t[]>
-utf8_to_wstr(char* str)
-{
- std::unique_ptr<wchar_t[]> ret;
- int size;
-
- size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
- if (size > 0) {
- ret.reset(new wchar_t[size]);
- ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size);
+static std::unique_ptr<wchar_t const []>
+utf8_to_wstr(char const * str) {
+ int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
+ if (size <= 0) {
+ return nullptr;
}
- return std::move(ret);
+ std::unique_ptr<wchar_t[]> ret(new wchar_t[size]);
+ ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size);
+ return ret;
}
-static IMMDevice *
-wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
+static com_ptr<IMMDevice> wasapi_get_device_node(
+ IMMDeviceEnumerator * enumerator, IMMDevice * dev)
{
- IMMDevice * ret = NULL;
- IDeviceTopology * devtopo = NULL;
- IConnector * connector = NULL;
-
- if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&devtopo)) &&
- SUCCEEDED(devtopo->GetConnector(0, &connector))) {
- LPWSTR filterid;
- if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&filterid))) {
- if (FAILED(enumerator->GetDevice(filterid, &ret)))
+ com_ptr<IMMDevice> ret;
+ com_ptr<IDeviceTopology> devtopo;
+ com_ptr<IConnector> connector;
+
+ if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL,
+ devtopo.receive_vpp())) &&
+ SUCCEEDED(devtopo->GetConnector(0, connector.receive()))) {
+ wchar_t * tmp = nullptr;
+ if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&tmp))) {
+ com_heap_ptr<wchar_t> filterid(tmp);
+ if (FAILED(enumerator->GetDevice(filterid.get(), ret.receive())))
ret = NULL;
- CoTaskMemFree(filterid);
}
}
- SafeRelease(connector);
- SafeRelease(devtopo);
return ret;
}
static BOOL
wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id,
- IMMDeviceEnumerator * enumerator)
+ IMMDeviceEnumerator * enumerator)
{
BOOL ret = FALSE;
- IMMDevice * dev;
+ com_ptr<IMMDevice> dev;
HRESULT hr;
- hr = enumerator->GetDefaultAudioEndpoint(flow, role, &dev);
+ hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive());
if (SUCCEEDED(hr)) {
- LPWSTR defdevid = NULL;
- if (SUCCEEDED(dev->GetId(&defdevid)))
- ret = (wcscmp(defdevid, device_id) == 0);
- if (defdevid != NULL)
- CoTaskMemFree(defdevid);
- SafeRelease(dev);
+ wchar_t * tmp = nullptr;
+ if (SUCCEEDED(dev->GetId(&tmp))) {
+ com_heap_ptr<wchar_t> defdevid(tmp);
+ ret = (wcscmp(defdevid.get(), device_id) == 0);
+ }
}
return ret;
}
-static cubeb_device_info *
-wasapi_create_device(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
+/* `ret` must be deallocated with `wasapi_destroy_device`, iff the return value
+ * of this function is `CUBEB_OK`. */
+int
+wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
+ IMMDeviceEnumerator * enumerator, IMMDevice * dev)
{
- IMMEndpoint * endpoint = NULL;
- IMMDevice * devnode = NULL;
- IAudioClient * client = NULL;
- cubeb_device_info * ret = NULL;
+ com_ptr<IMMEndpoint> endpoint;
+ com_ptr<IMMDevice> devnode;
+ com_ptr<IAudioClient> client;
EDataFlow flow;
- LPWSTR device_id = NULL;
DWORD state = DEVICE_STATE_NOTPRESENT;
- IPropertyStore * propstore = NULL;
- PROPVARIANT propvar;
+ com_ptr<IPropertyStore> propstore;
REFERENCE_TIME def_period, min_period;
HRESULT hr;
- PropVariantInit(&propvar);
+ // zero-out to be able to safely delete the pointers to friendly_name and
+ // group_id at all time in this function.
+ PodZero(&ret, 1);
- hr = dev->QueryInterface(IID_PPV_ARGS(&endpoint));
- if (FAILED(hr)) goto done;
+ struct prop_variant : public PROPVARIANT {
+ prop_variant() { PropVariantInit(this); }
+ ~prop_variant() { PropVariantClear(this); }
+ prop_variant(prop_variant const &) = delete;
+ prop_variant & operator=(prop_variant const &) = delete;
+ };
+
+ hr = dev->QueryInterface(IID_PPV_ARGS(endpoint.receive()));
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
hr = endpoint->GetDataFlow(&flow);
- if (FAILED(hr)) goto done;
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+
+ wchar_t * tmp = nullptr;
+ hr = dev->GetId(&tmp);
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+ com_heap_ptr<wchar_t> device_id(tmp);
- hr = dev->GetId(&device_id);
- if (FAILED(hr)) goto done;
+ char const * device_id_intern = intern_device_id(ctx, device_id.get());
+ if (!device_id_intern) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
- hr = dev->OpenPropertyStore(STGM_READ, &propstore);
- if (FAILED(hr)) goto done;
+ hr = dev->OpenPropertyStore(STGM_READ, propstore.receive());
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
hr = dev->GetState(&state);
- if (FAILED(hr)) goto done;
-
- ret = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info));
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
- ret->devid = ret->device_id = wstr_to_utf8(device_id);
- hr = propstore->GetValue(PKEY_Device_FriendlyName, &propvar);
- if (SUCCEEDED(hr))
- ret->friendly_name = wstr_to_utf8(propvar.pwszVal);
+ ret.device_id = device_id_intern;
+ ret.devid = reinterpret_cast<cubeb_devid>(ret.device_id);
+ prop_variant namevar;
+ hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar);
+ if (SUCCEEDED(hr) && namevar.vt == VT_LPWSTR) {
+ ret.friendly_name = wstr_to_utf8(namevar.pwszVal);
+ }
+ if (!ret.friendly_name) {
+ // This is not fatal, but a valid string is expected in all cases.
+ char * empty = new char[1];
+ empty[0] = '\0';
+ ret.friendly_name = empty;
+ }
devnode = wasapi_get_device_node(enumerator, dev);
- if (devnode != NULL) {
- IPropertyStore * ps = NULL;
- hr = devnode->OpenPropertyStore(STGM_READ, &ps);
- if (FAILED(hr)) goto done;
+ if (devnode) {
+ com_ptr<IPropertyStore> ps;
+ hr = devnode->OpenPropertyStore(STGM_READ, ps.receive());
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
- PropVariantClear(&propvar);
- hr = ps->GetValue(PKEY_Device_InstanceId, &propvar);
- if (SUCCEEDED(hr)) {
- ret->group_id = wstr_to_utf8(propvar.pwszVal);
+ prop_variant instancevar;
+ hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar);
+ if (SUCCEEDED(hr) && instancevar.vt == VT_LPWSTR) {
+ ret.group_id = wstr_to_utf8(instancevar.pwszVal);
}
- SafeRelease(ps);
}
- ret->preferred = CUBEB_DEVICE_PREF_NONE;
- if (wasapi_is_default_device(flow, eConsole, device_id, enumerator))
- ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_MULTIMEDIA);
- if (wasapi_is_default_device(flow, eCommunications, device_id, enumerator))
- ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_VOICE);
- if (wasapi_is_default_device(flow, eConsole, device_id, enumerator))
- ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_NOTIFICATION);
+ if (!ret.group_id) {
+ // This is not fatal, but a valid string is expected in all cases.
+ char * empty = new char[1];
+ empty[0] = '\0';
+ ret.group_id = empty;
+ }
- if (flow == eRender) ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
- else if (flow == eCapture) ret->type = CUBEB_DEVICE_TYPE_INPUT;
- switch (state) {
- case DEVICE_STATE_ACTIVE:
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- break;
- case DEVICE_STATE_UNPLUGGED:
- ret->state = CUBEB_DEVICE_STATE_UNPLUGGED;
- break;
- default:
- ret->state = CUBEB_DEVICE_STATE_DISABLED;
- break;
- };
+ ret.preferred = CUBEB_DEVICE_PREF_NONE;
+ if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) {
+ ret.preferred =
+ (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA);
+ }
+ if (wasapi_is_default_device(flow, eCommunications, device_id.get(),
+ enumerator)) {
+ ret.preferred =
+ (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE);
+ }
+ if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) {
+ ret.preferred =
+ (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_NOTIFICATION);
+ }
- ret->format = CUBEB_DEVICE_FMT_F32NE; /* cubeb only supports 32bit float at the moment */
- ret->default_format = CUBEB_DEVICE_FMT_F32NE;
- PropVariantClear(&propvar);
- hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &propvar);
- if (SUCCEEDED(hr) && propvar.vt == VT_BLOB) {
- if (propvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) {
- const PCMWAVEFORMAT * pcm = reinterpret_cast<const PCMWAVEFORMAT *>(propvar.blob.pBlobData);
+ if (flow == eRender) {
+ ret.type = CUBEB_DEVICE_TYPE_OUTPUT;
+ } else if (flow == eCapture) {
+ ret.type = CUBEB_DEVICE_TYPE_INPUT;
+ }
- ret->max_rate = ret->min_rate = ret->default_rate = pcm->wf.nSamplesPerSec;
- ret->max_channels = pcm->wf.nChannels;
- } else if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX)) {
- WAVEFORMATEX* wfx = reinterpret_cast<WAVEFORMATEX*>(propvar.blob.pBlobData);
+ switch (state) {
+ case DEVICE_STATE_ACTIVE:
+ ret.state = CUBEB_DEVICE_STATE_ENABLED;
+ break;
+ case DEVICE_STATE_UNPLUGGED:
+ ret.state = CUBEB_DEVICE_STATE_UNPLUGGED;
+ break;
+ default:
+ ret.state = CUBEB_DEVICE_STATE_DISABLED;
+ break;
+ };
- if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize ||
+ ret.format = static_cast<cubeb_device_fmt>(CUBEB_DEVICE_FMT_F32NE |
+ CUBEB_DEVICE_FMT_S16NE);
+ ret.default_format = CUBEB_DEVICE_FMT_F32NE;
+ prop_variant fmtvar;
+ hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar);
+ if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) {
+ if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) {
+ const PCMWAVEFORMAT * pcm =
+ reinterpret_cast<const PCMWAVEFORMAT *>(fmtvar.blob.pBlobData);
+
+ ret.max_rate = ret.min_rate = ret.default_rate = pcm->wf.nSamplesPerSec;
+ ret.max_channels = pcm->wf.nChannels;
+ } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) {
+ WAVEFORMATEX * wfx =
+ reinterpret_cast<WAVEFORMATEX *>(fmtvar.blob.pBlobData);
+
+ if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize ||
wfx->wFormatTag == WAVE_FORMAT_PCM) {
- ret->max_rate = ret->min_rate = ret->default_rate = wfx->nSamplesPerSec;
- ret->max_channels = wfx->nChannels;
+ ret.max_rate = ret.min_rate = ret.default_rate = wfx->nSamplesPerSec;
+ ret.max_channels = wfx->nChannels;
}
}
}
- if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&client)) &&
+ if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
+ NULL, client.receive_vpp())) &&
SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) {
- ret->latency_lo = hns_to_frames(ret->default_rate, min_period);
- ret->latency_hi = hns_to_frames(ret->default_rate, def_period);
+ ret.latency_lo = hns_to_frames(ret.default_rate, min_period);
+ ret.latency_hi = hns_to_frames(ret.default_rate, def_period);
} else {
- ret->latency_lo = 0;
- ret->latency_hi = 0;
- }
- SafeRelease(client);
-
-done:
- SafeRelease(devnode);
- SafeRelease(endpoint);
- SafeRelease(propstore);
- if (device_id != NULL)
- CoTaskMemFree(device_id);
- PropVariantClear(&propvar);
- return ret;
+ ret.latency_lo = 0;
+ ret.latency_hi = 0;
+ }
+
+ XASSERT(ret.friendly_name && ret.group_id);
+
+ return CUBEB_OK;
+}
+
+void
+wasapi_destroy_device(cubeb_device_info * device)
+{
+ delete[] device->friendly_name;
+ delete[] device->group_id;
}
static int
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** out)
+ cubeb_device_collection * out)
{
- auto_com com;
- IMMDeviceEnumerator * enumerator;
- IMMDeviceCollection * collection;
- IMMDevice * dev;
- cubeb_device_info * cur;
+ com_ptr<IMMDeviceEnumerator> enumerator;
+ com_ptr<IMMDeviceCollection> collection;
HRESULT hr;
UINT cc, i;
EDataFlow flow;
- *out = NULL;
-
- if (!com.ok())
- return CUBEB_ERROR;
-
- hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
- CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
+ hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(enumerator.receive()));
if (FAILED(hr)) {
- LOG("Could not get device enumerator: %x", hr);
+ LOG("Could not get device enumerator: %lx", hr);
return CUBEB_ERROR;
}
- if (type == CUBEB_DEVICE_TYPE_OUTPUT) flow = eRender;
- else if (type == CUBEB_DEVICE_TYPE_INPUT) flow = eCapture;
- else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_INPUT)) flow = eAll;
- else return CUBEB_ERROR;
+ if (type == CUBEB_DEVICE_TYPE_OUTPUT)
+ flow = eRender;
+ else if (type == CUBEB_DEVICE_TYPE_INPUT)
+ flow = eCapture;
+ else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT))
+ flow = eAll;
+ else
+ return CUBEB_ERROR;
- hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, &collection);
+ hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL,
+ collection.receive());
if (FAILED(hr)) {
- LOG("Could not enumerate audio endpoints: %x", hr);
+ LOG("Could not enumerate audio endpoints: %lx", hr);
return CUBEB_ERROR;
}
hr = collection->GetCount(&cc);
if (FAILED(hr)) {
- LOG("IMMDeviceCollection::GetCount() failed: %x", hr);
+ LOG("IMMDeviceCollection::GetCount() failed: %lx", hr);
return CUBEB_ERROR;
}
- *out = (cubeb_device_collection *) malloc(sizeof(cubeb_device_collection) +
- sizeof(cubeb_device_info*) * (cc > 0 ? cc - 1 : 0));
- if (!*out) {
+ cubeb_device_info * devices = new cubeb_device_info[cc];
+ if (!devices)
return CUBEB_ERROR;
- }
- (*out)->count = 0;
+
+ PodZero(devices, cc);
+ out->count = 0;
for (i = 0; i < cc; i++) {
- hr = collection->Item(i, &dev);
+ com_ptr<IMMDevice> dev;
+ hr = collection->Item(i, dev.receive());
if (FAILED(hr)) {
- LOG("IMMDeviceCollection::Item(%u) failed: %x", i-1, hr);
- } else if ((cur = wasapi_create_device(enumerator, dev)) != NULL) {
- (*out)->device[(*out)->count++] = cur;
+ LOG("IMMDeviceCollection::Item(%u) failed: %lx", i - 1, hr);
+ continue;
+ }
+ if (wasapi_create_device(context, devices[out->count], enumerator.get(),
+ dev.get()) == CUBEB_OK) {
+ out->count += 1;
+ }
+ }
+
+ out->device = devices;
+ return CUBEB_OK;
+}
+
+static int
+wasapi_device_collection_destroy(cubeb * /*ctx*/,
+ cubeb_device_collection * collection)
+{
+ XASSERT(collection);
+
+ for (size_t n = 0; n < collection->count; n++) {
+ cubeb_device_info & dev = collection->device[n];
+ wasapi_destroy_device(&dev);
+ }
+
+ delete[] collection->device;
+ return CUBEB_OK;
+}
+
+static int
+wasapi_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
+{
+ if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (collection_changed_callback) {
+ // Make sure it has been unregistered first.
+ XASSERT(((devtype & CUBEB_DEVICE_TYPE_INPUT) &&
+ !context->input_collection_changed_callback) ||
+ ((devtype & CUBEB_DEVICE_TYPE_OUTPUT) &&
+ !context->output_collection_changed_callback));
+
+ // Stop the notification client. Notifications arrive on
+ // a separate thread. We stop them here to avoid
+ // synchronization issues during the update.
+ if (context->device_collection_enumerator.get()) {
+ HRESULT hr = unregister_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = collection_changed_callback;
+ context->input_collection_changed_user_ptr = user_ptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = collection_changed_callback;
+ context->output_collection_changed_user_ptr = user_ptr;
+ }
+
+ HRESULT hr = register_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ } else {
+ if (!context->device_collection_enumerator.get()) {
+ // Already unregistered, ignore it.
+ return CUBEB_OK;
+ }
+
+ HRESULT hr = unregister_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = nullptr;
+ context->input_collection_changed_user_ptr = nullptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = nullptr;
+ context->output_collection_changed_user_ptr = nullptr;
+ }
+
+ // If after the updates we still have registered
+ // callbacks restart the notification client.
+ if (context->input_collection_changed_callback ||
+ context->output_collection_changed_callback) {
+ hr = register_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
}
}
- SafeRelease(collection);
- SafeRelease(enumerator);
return CUBEB_OK;
}
cubeb_ops const wasapi_ops = {
- /*.init =*/ wasapi_init,
- /*.get_backend_id =*/ wasapi_get_backend_id,
- /*.get_max_channel_count =*/ wasapi_get_max_channel_count,
- /*.get_min_latency =*/ wasapi_get_min_latency,
- /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate,
- /*.enumerate_devices =*/ wasapi_enumerate_devices,
- /*.destroy =*/ wasapi_destroy,
- /*.stream_init =*/ wasapi_stream_init,
- /*.stream_destroy =*/ wasapi_stream_destroy,
- /*.stream_start =*/ wasapi_stream_start,
- /*.stream_stop =*/ wasapi_stream_stop,
- /*.stream_get_position =*/ wasapi_stream_get_position,
- /*.stream_get_latency =*/ wasapi_stream_get_latency,
- /*.stream_set_volume =*/ wasapi_stream_set_volume,
- /*.stream_set_panning =*/ NULL,
- /*.stream_get_current_device =*/ NULL,
- /*.stream_device_destroy =*/ NULL,
- /*.stream_register_device_changed_callback =*/ NULL,
- /*.register_device_collection_changed =*/ NULL
+ /*.init =*/wasapi_init,
+ /*.get_backend_id =*/wasapi_get_backend_id,
+ /*.get_max_channel_count =*/wasapi_get_max_channel_count,
+ /*.get_min_latency =*/wasapi_get_min_latency,
+ /*.get_preferred_sample_rate =*/wasapi_get_preferred_sample_rate,
+ /*.enumerate_devices =*/wasapi_enumerate_devices,
+ /*.device_collection_destroy =*/wasapi_device_collection_destroy,
+ /*.destroy =*/wasapi_destroy,
+ /*.stream_init =*/wasapi_stream_init,
+ /*.stream_destroy =*/wasapi_stream_destroy,
+ /*.stream_start =*/wasapi_stream_start,
+ /*.stream_stop =*/wasapi_stream_stop,
+ /*.stream_get_position =*/wasapi_stream_get_position,
+ /*.stream_get_latency =*/wasapi_stream_get_latency,
+ /*.stream_get_input_latency =*/wasapi_stream_get_input_latency,
+ /*.stream_set_volume =*/wasapi_stream_set_volume,
+ /*.stream_set_name =*/NULL,
+ /*.stream_get_current_device =*/NULL,
+ /*.stream_device_destroy =*/NULL,
+ /*.stream_register_device_changed_callback =*/NULL,
+ /*.register_device_collection_changed =*/
+ wasapi_register_device_collection_changed,
};
-} // namespace anonymous
+} // namespace
diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c
deleted file mode 100644
index 585d11e89d..0000000000
--- a/media/libcubeb/src/cubeb_winmm.c
+++ /dev/null
@@ -1,1067 +0,0 @@
-/*
- * Copyright © 2011 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-#define __MSVCRT_VERSION__ 0x0700
-#undef WINVER
-#define WINVER 0x0501
-#undef WIN32_LEAN_AND_MEAN
-
-#include <malloc.h>
-#include <windows.h>
-#include <mmreg.h>
-#include <mmsystem.h>
-#include <process.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-#include "cubeb/cubeb.h"
-#include "cubeb-internal.h"
-
-/* This is missing from the MinGW headers. Use a safe fallback. */
-#if !defined(MEMORY_ALLOCATION_ALIGNMENT)
-#define MEMORY_ALLOCATION_ALIGNMENT 16
-#endif
-
-/**This is also missing from the MinGW headers. It also appears to be undocumented by Microsoft.*/
-#ifndef WAVE_FORMAT_48M08
-#define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */
-#endif
-#ifndef WAVE_FORMAT_48M16
-#define WAVE_FORMAT_48M16 0x00002000 /* 48 kHz, Mono, 16-bit */
-#endif
-#ifndef WAVE_FORMAT_48S08
-#define WAVE_FORMAT_48S08 0x00004000 /* 48 kHz, Stereo, 8-bit */
-#endif
-#ifndef WAVE_FORMAT_48S16
-#define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */
-#endif
-#ifndef WAVE_FORMAT_96M08
-#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */
-#endif
-#ifndef WAVE_FORMAT_96M16
-#define WAVE_FORMAT_96M16 0x00020000 /* 96 kHz, Mono, 16-bit */
-#endif
-#ifndef WAVE_FORMAT_96S08
-#define WAVE_FORMAT_96S08 0x00040000 /* 96 kHz, Stereo, 8-bit */
-#endif
-#ifndef WAVE_FORMAT_96S16
-#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */
-#endif
-
-/**Taken from winbase.h, also not in MinGW.*/
-#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
-#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only
-#endif
-
-#ifndef DRVM_MAPPER
-#define DRVM_MAPPER (0x2000)
-#endif
-#ifndef DRVM_MAPPER_PREFERRED_GET
-#define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER+21)
-#endif
-#ifndef DRVM_MAPPER_CONSOLEVOICECOM_GET
-#define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER+23)
-#endif
-
-#define CUBEB_STREAM_MAX 32
-#define NBUFS 4
-
-const GUID KSDATAFORMAT_SUBTYPE_PCM =
-{ 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
-const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT =
-{ 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
-
-struct cubeb_stream_item {
- SLIST_ENTRY head;
- cubeb_stream * stream;
-};
-
-static struct cubeb_ops const winmm_ops;
-
-struct cubeb {
- struct cubeb_ops const * ops;
- HANDLE event;
- HANDLE thread;
- int shutdown;
- PSLIST_HEADER work;
- CRITICAL_SECTION lock;
- unsigned int active_streams;
- unsigned int minimum_latency_ms;
-};
-
-struct cubeb_stream {
- cubeb * context;
- cubeb_stream_params params;
- cubeb_data_callback data_callback;
- cubeb_state_callback state_callback;
- void * user_ptr;
- WAVEHDR buffers[NBUFS];
- size_t buffer_size;
- int next_buffer;
- int free_buffers;
- int shutdown;
- int draining;
- HANDLE event;
- HWAVEOUT waveout;
- CRITICAL_SECTION lock;
- uint64_t written;
- float soft_volume;
-};
-
-static size_t
-bytes_per_frame(cubeb_stream_params params)
-{
- size_t bytes;
-
- switch (params.format) {
- case CUBEB_SAMPLE_S16LE:
- bytes = sizeof(signed short);
- break;
- case CUBEB_SAMPLE_FLOAT32LE:
- bytes = sizeof(float);
- break;
- default:
- XASSERT(0);
- }
-
- return bytes * params.channels;
-}
-
-static WAVEHDR *
-winmm_get_next_buffer(cubeb_stream * stm)
-{
- WAVEHDR * hdr = NULL;
-
- XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
- hdr = &stm->buffers[stm->next_buffer];
- XASSERT(hdr->dwFlags & WHDR_PREPARED ||
- (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE)));
- stm->next_buffer = (stm->next_buffer + 1) % NBUFS;
- stm->free_buffers -= 1;
-
- return hdr;
-}
-
-static void
-winmm_refill_stream(cubeb_stream * stm)
-{
- WAVEHDR * hdr;
- long got;
- long wanted;
- MMRESULT r;
-
- EnterCriticalSection(&stm->lock);
- stm->free_buffers += 1;
- XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
-
- if (stm->draining) {
- LeaveCriticalSection(&stm->lock);
- if (stm->free_buffers == NBUFS) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
- }
- SetEvent(stm->event);
- return;
- }
-
- if (stm->shutdown) {
- LeaveCriticalSection(&stm->lock);
- SetEvent(stm->event);
- return;
- }
-
- hdr = winmm_get_next_buffer(stm);
-
- wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params);
-
- /* It is assumed that the caller is holding this lock. It must be dropped
- during the callback to avoid deadlocks. */
- LeaveCriticalSection(&stm->lock);
- got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted);
- EnterCriticalSection(&stm->lock);
- if (got < 0) {
- LeaveCriticalSection(&stm->lock);
- /* XXX handle this case */
- XASSERT(0);
- return;
- } else if (got < wanted) {
- stm->draining = 1;
- }
- stm->written += got;
-
- XASSERT(hdr->dwFlags & WHDR_PREPARED);
-
- hdr->dwBufferLength = got * bytes_per_frame(stm->params);
- XASSERT(hdr->dwBufferLength <= stm->buffer_size);
-
- if (stm->soft_volume != -1.0) {
- if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
- float * b = (float *) hdr->lpData;
- uint32_t i;
- for (i = 0; i < got * stm->params.channels; i++) {
- b[i] *= stm->soft_volume;
- }
- } else {
- short * b = (short *) hdr->lpData;
- uint32_t i;
- for (i = 0; i < got * stm->params.channels; i++) {
- b[i] = (short) (b[i] * stm->soft_volume);
- }
- }
- }
-
- r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr));
- if (r != MMSYSERR_NOERROR) {
- LeaveCriticalSection(&stm->lock);
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- return;
- }
-
- LeaveCriticalSection(&stm->lock);
-}
-
-static unsigned __stdcall
-winmm_buffer_thread(void * user_ptr)
-{
- cubeb * ctx = (cubeb *) user_ptr;
- XASSERT(ctx);
-
- for (;;) {
- DWORD r;
- PSLIST_ENTRY item;
-
- r = WaitForSingleObject(ctx->event, INFINITE);
- XASSERT(r == WAIT_OBJECT_0);
-
- /* Process work items in batches so that a single stream can't
- starve the others by continuously adding new work to the top of
- the work item stack. */
- item = InterlockedFlushSList(ctx->work);
- while (item != NULL) {
- PSLIST_ENTRY tmp = item;
- winmm_refill_stream(((struct cubeb_stream_item *) tmp)->stream);
- item = item->Next;
- _aligned_free(tmp);
- }
-
- if (ctx->shutdown) {
- break;
- }
- }
-
- return 0;
-}
-
-static void CALLBACK
-winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR p1, DWORD_PTR p2)
-{
- cubeb_stream * stm = (cubeb_stream *) user_ptr;
- struct cubeb_stream_item * item;
-
- if (msg != WOM_DONE) {
- return;
- }
-
- item = _aligned_malloc(sizeof(struct cubeb_stream_item), MEMORY_ALLOCATION_ALIGNMENT);
- XASSERT(item);
- item->stream = stm;
- InterlockedPushEntrySList(stm->context->work, &item->head);
-
- SetEvent(stm->context->event);
-}
-
-static unsigned int
-calculate_minimum_latency(void)
-{
- OSVERSIONINFOEX osvi;
- DWORDLONG mask;
-
- /* Running under Terminal Services results in underruns with low latency. */
- if (GetSystemMetrics(SM_REMOTESESSION) == TRUE) {
- return 500;
- }
-
- /* Vista's WinMM implementation underruns when less than 200ms of audio is buffered. */
- memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
- osvi.dwMajorVersion = 6;
- osvi.dwMinorVersion = 0;
-
- mask = 0;
- VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL);
- VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL);
-
- if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) {
- return 200;
- }
-
- return 100;
-}
-
-static void winmm_destroy(cubeb * ctx);
-
-/*static*/ int
-winmm_init(cubeb ** context, char const * context_name)
-{
- cubeb * ctx;
-
- XASSERT(context);
- *context = NULL;
-
- /* Don't initialize a context if there are no devices available. */
- if (waveOutGetNumDevs() == 0) {
- return CUBEB_ERROR;
- }
-
- ctx = calloc(1, sizeof(*ctx));
- XASSERT(ctx);
-
- ctx->ops = &winmm_ops;
-
- ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT);
- XASSERT(ctx->work);
- InitializeSListHead(ctx->work);
-
- ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (!ctx->event) {
- winmm_destroy(ctx);
- return CUBEB_ERROR;
- }
-
- ctx->thread = (HANDLE) _beginthreadex(NULL, 256 * 1024, winmm_buffer_thread, ctx, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
- if (!ctx->thread) {
- winmm_destroy(ctx);
- return CUBEB_ERROR;
- }
-
- SetThreadPriority(ctx->thread, THREAD_PRIORITY_TIME_CRITICAL);
-
- InitializeCriticalSection(&ctx->lock);
- ctx->active_streams = 0;
-
- ctx->minimum_latency_ms = calculate_minimum_latency();
-
- *context = ctx;
-
- return CUBEB_OK;
-}
-
-static char const *
-winmm_get_backend_id(cubeb * ctx)
-{
- return "winmm";
-}
-
-static void
-winmm_destroy(cubeb * ctx)
-{
- DWORD r;
-
- XASSERT(ctx->active_streams == 0);
- XASSERT(!InterlockedPopEntrySList(ctx->work));
-
- DeleteCriticalSection(&ctx->lock);
-
- if (ctx->thread) {
- ctx->shutdown = 1;
- SetEvent(ctx->event);
- r = WaitForSingleObject(ctx->thread, INFINITE);
- XASSERT(r == WAIT_OBJECT_0);
- CloseHandle(ctx->thread);
- }
-
- if (ctx->event) {
- CloseHandle(ctx->event);
- }
-
- _aligned_free(ctx->work);
-
- free(ctx);
-}
-
-static void winmm_stream_destroy(cubeb_stream * stm);
-
-static int
-winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
-{
- MMRESULT r;
- WAVEFORMATEXTENSIBLE wfx;
- cubeb_stream * stm;
- int i;
- size_t bufsz;
-
- XASSERT(context);
- XASSERT(stream);
-
- if (input_stream_params) {
- /* Capture support not yet implemented. */
- return CUBEB_ERROR_NOT_SUPPORTED;
- }
-
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
- }
-
- *stream = NULL;
-
- memset(&wfx, 0, sizeof(wfx));
- if (output_stream_params->channels > 2) {
- wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format);
- } else {
- wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
- if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
- wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
- }
- wfx.Format.cbSize = 0;
- }
- wfx.Format.nChannels = output_stream_params->channels;
- wfx.Format.nSamplesPerSec = output_stream_params->rate;
-
- /* XXX fix channel mappings */
- wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
-
- switch (output_stream_params->format) {
- case CUBEB_SAMPLE_S16LE:
- wfx.Format.wBitsPerSample = 16;
- wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case CUBEB_SAMPLE_FLOAT32LE:
- wfx.Format.wBitsPerSample = 32;
- wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- break;
- default:
- return CUBEB_ERROR_INVALID_FORMAT;
- }
-
- wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
- wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
- wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
-
- EnterCriticalSection(&context->lock);
- /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when
- many streams are active at once, a subset of them will not consume (via
- playback) or release (via waveOutReset) their buffers. */
- if (context->active_streams >= CUBEB_STREAM_MAX) {
- LeaveCriticalSection(&context->lock);
- return CUBEB_ERROR;
- }
- context->active_streams += 1;
- LeaveCriticalSection(&context->lock);
-
- stm = calloc(1, sizeof(*stm));
- XASSERT(stm);
-
- stm->context = context;
-
- stm->params = *output_stream_params;
-
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
- stm->written = 0;
-
- uint32_t latency_ms = latency_frames * 1000 / output_stream_params->rate;
-
- if (latency_ms < context->minimum_latency_ms) {
- latency_ms = context->minimum_latency_ms;
- }
-
- bufsz = (size_t) (stm->params.rate / 1000.0 * latency_ms * bytes_per_frame(stm->params) / NBUFS);
- if (bufsz % bytes_per_frame(stm->params) != 0) {
- bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params));
- }
- XASSERT(bufsz % bytes_per_frame(stm->params) == 0);
-
- stm->buffer_size = bufsz;
-
- InitializeCriticalSection(&stm->lock);
-
- stm->event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (!stm->event) {
- winmm_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- stm->soft_volume = -1.0;
-
- /* winmm_buffer_callback will be called during waveOutOpen, so all
- other initialization must be complete before calling it. */
- r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
- (DWORD_PTR) winmm_buffer_callback, (DWORD_PTR) stm,
- CALLBACK_FUNCTION);
- if (r != MMSYSERR_NOERROR) {
- winmm_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- r = waveOutPause(stm->waveout);
- if (r != MMSYSERR_NOERROR) {
- winmm_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
-
- for (i = 0; i < NBUFS; ++i) {
- WAVEHDR * hdr = &stm->buffers[i];
-
- hdr->lpData = calloc(1, bufsz);
- XASSERT(hdr->lpData);
- hdr->dwBufferLength = bufsz;
- hdr->dwFlags = 0;
-
- r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr));
- if (r != MMSYSERR_NOERROR) {
- winmm_stream_destroy(stm);
- return CUBEB_ERROR;
- }
-
- winmm_refill_stream(stm);
- }
-
- *stream = stm;
-
- return CUBEB_OK;
-}
-
-static void
-winmm_stream_destroy(cubeb_stream * stm)
-{
- int i;
-
- if (stm->waveout) {
- MMTIME time;
- MMRESULT r;
- int device_valid;
- int enqueued;
-
- EnterCriticalSection(&stm->lock);
- stm->shutdown = 1;
-
- waveOutReset(stm->waveout);
-
- /* Don't need this value, we just want the result to detect invalid
- handle/no device errors than waveOutReset doesn't seem to report. */
- time.wType = TIME_SAMPLES;
- r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
- device_valid = !(r == MMSYSERR_INVALHANDLE || r == MMSYSERR_NODRIVER);
-
- enqueued = NBUFS - stm->free_buffers;
- LeaveCriticalSection(&stm->lock);
-
- /* Wait for all blocks to complete. */
- while (device_valid && enqueued > 0) {
- DWORD rv = WaitForSingleObject(stm->event, INFINITE);
- XASSERT(rv == WAIT_OBJECT_0);
-
- EnterCriticalSection(&stm->lock);
- enqueued = NBUFS - stm->free_buffers;
- LeaveCriticalSection(&stm->lock);
- }
-
- EnterCriticalSection(&stm->lock);
-
- for (i = 0; i < NBUFS; ++i) {
- if (stm->buffers[i].dwFlags & WHDR_PREPARED) {
- waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i]));
- }
- }
-
- waveOutClose(stm->waveout);
-
- LeaveCriticalSection(&stm->lock);
- }
-
- if (stm->event) {
- CloseHandle(stm->event);
- }
-
- DeleteCriticalSection(&stm->lock);
-
- for (i = 0; i < NBUFS; ++i) {
- free(stm->buffers[i].lpData);
- }
-
- EnterCriticalSection(&stm->context->lock);
- XASSERT(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
- LeaveCriticalSection(&stm->context->lock);
-
- free(stm);
-}
-
-static int
-winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
-{
- XASSERT(ctx && max_channels);
-
- /* We don't support more than two channels in this backend. */
- *max_channels = 2;
-
- return CUBEB_OK;
-}
-
-static int
-winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
-{
- // 100ms minimum, if we are not in a bizarre configuration.
- *latency = ctx->minimum_latency_ms * params.rate / 1000;
-
- return CUBEB_OK;
-}
-
-static int
-winmm_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
-{
- WAVEOUTCAPS woc;
- MMRESULT r;
-
- r = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
- if (r != MMSYSERR_NOERROR) {
- return CUBEB_ERROR;
- }
-
- /* Check if we support 48kHz, but not 44.1kHz. */
- if (!(woc.dwFormats & WAVE_FORMAT_4S16) &&
- woc.dwFormats & WAVE_FORMAT_48S16) {
- *rate = 48000;
- return CUBEB_OK;
- }
- /* Prefer 44.1kHz between 44.1kHz and 48kHz. */
- *rate = 44100;
-
- return CUBEB_OK;
-}
-
-static int
-winmm_stream_start(cubeb_stream * stm)
-{
- MMRESULT r;
-
- EnterCriticalSection(&stm->lock);
- r = waveOutRestart(stm->waveout);
- LeaveCriticalSection(&stm->lock);
-
- if (r != MMSYSERR_NOERROR) {
- return CUBEB_ERROR;
- }
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
-
- return CUBEB_OK;
-}
-
-static int
-winmm_stream_stop(cubeb_stream * stm)
-{
- MMRESULT r;
-
- EnterCriticalSection(&stm->lock);
- r = waveOutPause(stm->waveout);
- LeaveCriticalSection(&stm->lock);
-
- if (r != MMSYSERR_NOERROR) {
- return CUBEB_ERROR;
- }
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-
- return CUBEB_OK;
-}
-
-static int
-winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
-{
- MMRESULT r;
- MMTIME time;
-
- EnterCriticalSection(&stm->lock);
- time.wType = TIME_SAMPLES;
- r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
- LeaveCriticalSection(&stm->lock);
-
- if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
- return CUBEB_ERROR;
- }
-
- *position = time.u.sample;
-
- return CUBEB_OK;
-}
-
-static int
-winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
-{
- MMRESULT r;
- MMTIME time;
- uint64_t written;
-
- EnterCriticalSection(&stm->lock);
- time.wType = TIME_SAMPLES;
- r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
- written = stm->written;
- LeaveCriticalSection(&stm->lock);
-
- if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
- return CUBEB_ERROR;
- }
-
- XASSERT(written - time.u.sample <= UINT32_MAX);
- *latency = (uint32_t) (written - time.u.sample);
-
- return CUBEB_OK;
-}
-
-static int
-winmm_stream_set_volume(cubeb_stream * stm, float volume)
-{
- EnterCriticalSection(&stm->lock);
- stm->soft_volume = volume;
- LeaveCriticalSection(&stm->lock);
- return CUBEB_OK;
-}
-
-#define MM_11025HZ_MASK (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16)
-#define MM_22050HZ_MASK (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16)
-#define MM_44100HZ_MASK (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16)
-#define MM_48000HZ_MASK (WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48S16)
-#define MM_96000HZ_MASK (WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96S16)
-static void
-winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats)
-{
- if (formats & MM_11025HZ_MASK) {
- info->min_rate = 11025;
- info->default_rate = 11025;
- info->max_rate = 11025;
- }
- if (formats & MM_22050HZ_MASK) {
- if (info->min_rate == 0) info->min_rate = 22050;
- info->max_rate = 22050;
- info->default_rate = 22050;
- }
- if (formats & MM_44100HZ_MASK) {
- if (info->min_rate == 0) info->min_rate = 44100;
- info->max_rate = 44100;
- info->default_rate = 44100;
- }
- if (formats & MM_48000HZ_MASK) {
- if (info->min_rate == 0) info->min_rate = 48000;
- info->max_rate = 48000;
- info->default_rate = 48000;
- }
- if (formats & MM_96000HZ_MASK) {
- if (info->min_rate == 0) {
- info->min_rate = 96000;
- info->default_rate = 96000;
- }
- info->max_rate = 96000;
- }
-}
-
-
-#define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \
- WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16)
-static int
-winmm_query_supported_formats(UINT devid, DWORD formats,
- cubeb_device_fmt * supfmt, cubeb_device_fmt * deffmt)
-{
- WAVEFORMATEXTENSIBLE wfx;
-
- if (formats & MM_S16_MASK)
- *deffmt = *supfmt = CUBEB_DEVICE_FMT_S16LE;
- else
- *deffmt = *supfmt = 0;
-
- ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
- wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- wfx.Format.nChannels = 2;
- wfx.Format.nSamplesPerSec = 44100;
- wfx.Format.wBitsPerSample = 32;
- wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
- wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
- wfx.Format.cbSize = 22;
- wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
- wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
- wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- if (waveOutOpen(NULL, devid, &wfx.Format, 0, 0, WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR)
- *supfmt = (cubeb_device_fmt)(*supfmt | CUBEB_DEVICE_FMT_F32LE);
-
- return (*deffmt != 0) ? CUBEB_OK : CUBEB_ERROR;
-}
-
-static char *
-guid_to_cstr(LPGUID guid)
-{
- char * ret = malloc(sizeof(char) * 40);
- if (!ret) {
- return NULL;
- }
- _snprintf_s(ret, sizeof(char) * 40, _TRUNCATE,
- "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
- guid->Data1, guid->Data2, guid->Data3,
- guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
- guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
- return ret;
-}
-
-static cubeb_device_pref
-winmm_query_preferred_out_device(UINT devid)
-{
- DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
- cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
-
- if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
- (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == mmpref)
- ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
-
- if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
- (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == compref)
- ret |= CUBEB_DEVICE_PREF_VOICE;
-
- return ret;
-}
-
-static char *
-device_id_idx(UINT devid)
-{
- char * ret = (char *)malloc(sizeof(char)*16);
- if (!ret) {
- return NULL;
- }
- _snprintf_s(ret, 16, _TRUNCATE, "%u", devid);
- return ret;
-}
-
-static cubeb_device_info *
-winmm_create_device_from_outcaps2(LPWAVEOUTCAPS2A caps, UINT devid)
-{
- cubeb_device_info * ret;
-
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = guid_to_cstr(&caps->ProductGuid);
- ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
-
- ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_out_device(devid);
-
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
-
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
-
- return ret;
-}
-
-static cubeb_device_info *
-winmm_create_device_from_outcaps(LPWAVEOUTCAPSA caps, UINT devid)
-{
- cubeb_device_info * ret;
-
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = NULL;
- ret->vendor_name = NULL;
-
- ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_out_device(devid);
-
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
-
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
-
- return ret;
-}
-
-static cubeb_device_pref
-winmm_query_preferred_in_device(UINT devid)
-{
- DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
- cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
-
- if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
- (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == mmpref)
- ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
-
- if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
- (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == compref)
- ret |= CUBEB_DEVICE_PREF_VOICE;
-
- return ret;
-}
-
-static cubeb_device_info *
-winmm_create_device_from_incaps2(LPWAVEINCAPS2A caps, UINT devid)
-{
- cubeb_device_info * ret;
-
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = guid_to_cstr(&caps->ProductGuid);
- ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
-
- ret->type = CUBEB_DEVICE_TYPE_INPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_in_device(devid);
-
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
-
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
-
- return ret;
-}
-
-static cubeb_device_info *
-winmm_create_device_from_incaps(LPWAVEINCAPSA caps, UINT devid)
-{
- cubeb_device_info * ret;
-
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = NULL;
- ret->vendor_name = NULL;
-
- ret->type = CUBEB_DEVICE_TYPE_INPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_in_device(devid);
-
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
-
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
-
- return ret;
-}
-
-static int
-winmm_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection)
-{
- UINT i, incount, outcount, total;
- cubeb_device_info * cur;
-
- outcount = waveOutGetNumDevs();
- incount = waveInGetNumDevs();
- total = outcount + incount;
- if (total > 0) {
- total -= 1;
- }
- *collection = malloc(sizeof(cubeb_device_collection) +
- sizeof(cubeb_device_info*) * total);
- (*collection)->count = 0;
-
- if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
- WAVEOUTCAPSA woc;
- WAVEOUTCAPS2A woc2;
-
- ZeroMemory(&woc, sizeof(woc));
- ZeroMemory(&woc2, sizeof(woc2));
-
- for (i = 0; i < outcount; i++) {
- if ((waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_outcaps2(&woc2, i)) != NULL) ||
- (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_outcaps(&woc, i)) != NULL)
- ) {
- (*collection)->device[(*collection)->count++] = cur;
- }
- }
- }
-
- if (type & CUBEB_DEVICE_TYPE_INPUT) {
- WAVEINCAPSA wic;
- WAVEINCAPS2A wic2;
-
- ZeroMemory(&wic, sizeof(wic));
- ZeroMemory(&wic2, sizeof(wic2));
-
- for (i = 0; i < incount; i++) {
- if ((waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_incaps2(&wic2, i)) != NULL) ||
- (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_incaps(&wic, i)) != NULL)
- ) {
- (*collection)->device[(*collection)->count++] = cur;
- }
- }
- }
-
- return CUBEB_OK;
-}
-
-static struct cubeb_ops const winmm_ops = {
- /*.init =*/ winmm_init,
- /*.get_backend_id =*/ winmm_get_backend_id,
- /*.get_max_channel_count=*/ winmm_get_max_channel_count,
- /*.get_min_latency=*/ winmm_get_min_latency,
- /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate,
- /*.enumerate_devices =*/ winmm_enumerate_devices,
- /*.destroy =*/ winmm_destroy,
- /*.stream_init =*/ winmm_stream_init,
- /*.stream_destroy =*/ winmm_stream_destroy,
- /*.stream_start =*/ winmm_stream_start,
- /*.stream_stop =*/ winmm_stream_stop,
- /*.stream_get_position =*/ winmm_stream_get_position,
- /*.stream_get_latency = */ winmm_stream_get_latency,
- /*.stream_set_volume =*/ winmm_stream_set_volume,
- /*.stream_set_panning =*/ NULL,
- /*.stream_get_current_device =*/ NULL,
- /*.stream_device_destroy =*/ NULL,
- /*.stream_register_device_changed_callback=*/ NULL,
- /*.register_device_collection_changed =*/ NULL
-};
diff --git a/media/libcubeb/src/moz.build b/media/libcubeb/src/moz.build
index c21cc873db..a67098d8dd 100644
--- a/media/libcubeb/src/moz.build
+++ b/media/libcubeb/src/moz.build
@@ -9,7 +9,11 @@ Library('cubeb')
SOURCES += [
'cubeb.c',
- 'cubeb_panner.cpp'
+ 'cubeb_log.cpp',
+ 'cubeb_mixer.cpp',
+ 'cubeb_panner.cpp',
+ 'cubeb_strings.c',
+ 'cubeb_utils.cpp'
]
if CONFIG['MOZ_ALSA']:
@@ -23,12 +27,6 @@ if CONFIG['MOZ_PULSEAUDIO'] or CONFIG['MOZ_JACK']:
'cubeb_resampler.cpp',
]
-if CONFIG['MOZ_PULSEAUDIO']:
- SOURCES += [
- 'cubeb_pulse.c',
- ]
- DEFINES['USE_PULSE'] = True
-
if CONFIG['MOZ_JACK']:
SOURCES += [
'cubeb_jack.cpp',
@@ -38,51 +36,32 @@ if CONFIG['MOZ_JACK']:
]
DEFINES['USE_JACK'] = True
-if CONFIG['MOZ_SNDIO']:
+if CONFIG['OS_ARCH'] in ('DragonFly', 'FreeBSD', 'SunOS'):
SOURCES += [
- 'cubeb_sndio.c',
+ 'cubeb_oss.c',
]
- DEFINES['USE_SNDIO'] = True
-
-if CONFIG['OS_ARCH'] == 'SunOS':
- SOURCES += [
- 'cubeb_sun.c',
- ]
- DEFINES['USE_SUN'] = True
-
-if CONFIG['OS_TARGET'] == 'Darwin':
- SOURCES += [
- 'cubeb_audiounit.cpp',
- 'cubeb_resampler.cpp'
- ]
- DEFINES['USE_AUDIOUNIT'] = True
+ DEFINES['USE_OSS'] = True
if CONFIG['OS_TARGET'] == 'WINNT':
SOURCES += [
'cubeb_resampler.cpp',
'cubeb_wasapi.cpp',
- 'cubeb_winmm.c',
]
- DEFINES['USE_WINMM'] = True
+ DEFINES['UNICODE'] = True
DEFINES['USE_WASAPI'] = True
+ OS_LIBS += [
+ "avrt",
+ ]
if CONFIG['_MSC_VER']:
CXXFLAGS += ['-wd4005'] # C4005: '_USE_MATH_DEFINES' : macro redefinition
-if CONFIG['OS_TARGET'] == 'Android':
- SOURCES += ['cubeb_opensl.c']
- SOURCES += ['cubeb_resampler.cpp']
- DEFINES['USE_OPENSL'] = True
- SOURCES += [
- 'cubeb_audiotrack.c',
- ]
- DEFINES['USE_AUDIOTRACK'] = True
-
if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
NO_VISIBILITY_FLAGS = True
FINAL_LIBRARY = 'gkmedias'
CFLAGS += CONFIG['MOZ_ALSA_CFLAGS']
+CFLAGS += CONFIG['MOZ_JACK_CFLAGS']
CFLAGS += CONFIG['MOZ_PULSEAUDIO_CFLAGS']
# We allow warnings for third-party code that can be updated from upstream.
diff --git a/media/libcubeb/tests/common.h b/media/libcubeb/tests/common.h
deleted file mode 100644
index 051f3c42a2..0000000000
--- a/media/libcubeb/tests/common.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright © 2013 Sebastien Alaiwan
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-#if defined( _WIN32)
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif
-#include <windows.h>
-#else
-#include <unistd.h>
-#endif
-
-void delay(unsigned int ms)
-{
-#if defined(_WIN32)
- Sleep(ms);
-#else
- sleep(ms / 1000);
- usleep(ms % 1000 * 1000);
-#endif
-}
-
-#if !defined(M_PI)
-#define M_PI 3.14159265358979323846
-#endif
-
-int has_available_input_device(cubeb * ctx)
-{
- cubeb_device_collection * devices;
- int input_device_available = 0;
- int r;
- /* Bail out early if the host does not have input devices. */
- r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices);
- if (r != CUBEB_OK) {
- fprintf(stderr, "error enumerating devices.");
- return 0;
- }
-
- if (devices->count == 0) {
- fprintf(stderr, "no input device available, skipping test.\n");
- return 0;
- }
-
- for (uint32_t i = 0; i < devices->count; i++) {
- input_device_available |= (devices->device[i]->state ==
- CUBEB_DEVICE_STATE_ENABLED);
- }
-
- if (!input_device_available) {
- fprintf(stderr, "there are input devices, but they are not "
- "available, skipping\n");
- return 0;
- }
-
- return 1;
-}
-
diff --git a/media/libcubeb/tests/test_audio.cpp b/media/libcubeb/tests/test_audio.cpp
deleted file mode 100644
index 4943223e68..0000000000
--- a/media/libcubeb/tests/test_audio.cpp
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright © 2013 Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-/* libcubeb api/function exhaustive test. Plays a series of tones in different
- * conditions. */
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-#define _XOPEN_SOURCE 600
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-#include <assert.h>
-#include <string.h>
-
-#include "cubeb/cubeb.h"
-#include "common.h"
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
-
-#define MAX_NUM_CHANNELS 32
-
-#if !defined(M_PI)
-#define M_PI 3.14159265358979323846
-#endif
-
-#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
-#define VOLUME 0.2
-
-float get_frequency(int channel_index)
-{
- return 220.0f * (channel_index+1);
-}
-
-/* store the phase of the generated waveform */
-typedef struct {
- int num_channels;
- float phase[MAX_NUM_CHANNELS];
- float sample_rate;
-} synth_state;
-
-synth_state* synth_create(int num_channels, float sample_rate)
-{
- synth_state* synth = (synth_state *) malloc(sizeof(synth_state));
- if (!synth)
- return NULL;
- for(int i=0;i < MAX_NUM_CHANNELS;++i)
- synth->phase[i] = 0.0f;
- synth->num_channels = num_channels;
- synth->sample_rate = sample_rate;
- return synth;
-}
-
-void synth_destroy(synth_state* synth)
-{
- free(synth);
-}
-
-void synth_run_float(synth_state* synth, float* audiobuffer, long nframes)
-{
- for(int c=0;c < synth->num_channels;++c) {
- float freq = get_frequency(c);
- float phase_inc = 2.0 * M_PI * freq / synth->sample_rate;
- for(long n=0;n < nframes;++n) {
- audiobuffer[n*synth->num_channels+c] = sin(synth->phase[c]) * VOLUME;
- synth->phase[c] += phase_inc;
- }
- }
-}
-
-long data_cb_float(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
-{
- synth_state *synth = (synth_state *)user;
- synth_run_float(synth, (float*)outputbuffer, nframes);
- return nframes;
-}
-
-void synth_run_16bit(synth_state* synth, short* audiobuffer, long nframes)
-{
- for(int c=0;c < synth->num_channels;++c) {
- float freq = get_frequency(c);
- float phase_inc = 2.0 * M_PI * freq / synth->sample_rate;
- for(long n=0;n < nframes;++n) {
- audiobuffer[n*synth->num_channels+c] = sin(synth->phase[c]) * VOLUME * 32767.0f;
- synth->phase[c] += phase_inc;
- }
- }
-}
-
-long data_cb_short(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
-{
- synth_state *synth = (synth_state *)user;
- synth_run_16bit(synth, (short*)outputbuffer, nframes);
- return nframes;
-}
-
-void state_cb(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
-{
-}
-
-/* Our android backends don't support float, only int16. */
-int supports_float32(const char* backend_id)
-{
- return (strcmp(backend_id, "opensl") != 0 &&
- strcmp(backend_id, "audiotrack") != 0);
-}
-
-/* The WASAPI backend only supports float. */
-int supports_int16(const char* backend_id)
-{
- return strcmp(backend_id, "wasapi") != 0;
-}
-
-/* Some backends don't have code to deal with more than mono or stereo. */
-int supports_channel_count(const char* backend_id, int nchannels)
-{
- return nchannels <= 2 ||
- (strcmp(backend_id, "opensl") != 0 && strcmp(backend_id, "audiotrack") != 0);
-}
-
-int run_test(int num_channels, int sampling_rate, int is_float)
-{
- int r = CUBEB_OK;
-
- cubeb *ctx = NULL;
- synth_state* synth = NULL;
- cubeb_stream *stream = NULL;
- const char * backend_id = NULL;
-
- r = cubeb_init(&ctx, "Cubeb audio test: channels");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- goto cleanup;
- }
-
- backend_id = cubeb_get_backend_id(ctx);
-
- if ((is_float && !supports_float32(backend_id)) ||
- (!is_float && !supports_int16(backend_id)) ||
- !supports_channel_count(backend_id, num_channels)) {
- /* don't treat this as a test failure. */
- goto cleanup;
- }
-
- fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
-
- cubeb_stream_params params;
- params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
- params.rate = sampling_rate;
- params.channels = num_channels;
-
- synth = synth_create(params.channels, params.rate);
- if (synth == NULL) {
- fprintf(stderr, "Out of memory\n");
- goto cleanup;
- }
-
- r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
- 4096, is_float ? data_cb_float : data_cb_short, state_cb, synth);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
- goto cleanup;
- }
-
- cubeb_stream_start(stream);
- delay(200);
- cubeb_stream_stop(stream);
-
-cleanup:
- cubeb_stream_destroy(stream);
- cubeb_destroy(ctx);
- synth_destroy(synth);
-
- return r;
-}
-
-int run_panning_volume_test(int is_float)
-{
- int r = CUBEB_OK;
-
- cubeb *ctx = NULL;
- synth_state* synth = NULL;
- cubeb_stream *stream = NULL;
- const char * backend_id = NULL;
-
- r = cubeb_init(&ctx, "Cubeb audio test");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- goto cleanup;
- }
- backend_id = cubeb_get_backend_id(ctx);
-
- if ((is_float && !supports_float32(backend_id)) ||
- (!is_float && !supports_int16(backend_id))) {
- /* don't treat this as a test failure. */
- goto cleanup;
- }
-
- cubeb_stream_params params;
- params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
- params.rate = 44100;
- params.channels = 2;
-
- synth = synth_create(params.channels, params.rate);
- if (synth == NULL) {
- fprintf(stderr, "Out of memory\n");
- goto cleanup;
- }
-
- r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
- 4096, is_float ? data_cb_float : data_cb_short,
- state_cb, synth);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
- goto cleanup;
- }
-
- fprintf(stderr, "Testing: volume\n");
- for(int i=0;i <= 4; ++i)
- {
- fprintf(stderr, "Volume: %d%%\n", i*25);
-
- cubeb_stream_set_volume(stream, i/4.0f);
- cubeb_stream_start(stream);
- delay(400);
- cubeb_stream_stop(stream);
- delay(100);
- }
-
- fprintf(stderr, "Testing: panning\n");
- for(int i=-4;i <= 4; ++i)
- {
- fprintf(stderr, "Panning: %.2f%%\n", i/4.0f);
-
- cubeb_stream_set_panning(stream, i/4.0f);
- cubeb_stream_start(stream);
- delay(400);
- cubeb_stream_stop(stream);
- delay(100);
- }
-
-cleanup:
- cubeb_stream_destroy(stream);
- cubeb_destroy(ctx);
- synth_destroy(synth);
-
- return r;
-}
-
-void run_channel_rate_test()
-{
- int channel_values[] = {
- 1,
- 2,
- 3,
- 4,
- 6,
- };
-
- int freq_values[] = {
- 16000,
- 24000,
- 44100,
- 48000,
- };
-
- for(int j = 0; j < NELEMS(channel_values); ++j) {
- for(int i = 0; i < NELEMS(freq_values); ++i) {
- assert(channel_values[j] < MAX_NUM_CHANNELS);
- fprintf(stderr, "--------------------------\n");
- assert(run_test(channel_values[j], freq_values[i], 0) == CUBEB_OK);
- assert(run_test(channel_values[j], freq_values[i], 1) == CUBEB_OK);
- }
- }
-}
-
-
-int main(int /*argc*/, char * /*argv*/[])
-{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_audio");
-#endif
-
- assert(run_panning_volume_test(0) == CUBEB_OK);
- assert(run_panning_volume_test(1) == CUBEB_OK);
- run_channel_rate_test();
-
- return CUBEB_OK;
-}
diff --git a/media/libcubeb/tests/test_devices.cpp b/media/libcubeb/tests/test_devices.cpp
deleted file mode 100644
index 9c502c0afd..0000000000
--- a/media/libcubeb/tests/test_devices.cpp
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright © 2015 Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-/* libcubeb enumerate device test/example.
- * Prints out a list of devices enumerated. */
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "cubeb/cubeb.h"
-
-
-static void
-print_device_info(cubeb_device_info * info, FILE * f)
-{
- char devfmts[64] = "";
- const char * devtype, * devstate, * devdeffmt;
-
- switch (info->type) {
- case CUBEB_DEVICE_TYPE_INPUT:
- devtype = "input";
- break;
- case CUBEB_DEVICE_TYPE_OUTPUT:
- devtype = "output";
- break;
- case CUBEB_DEVICE_TYPE_UNKNOWN:
- default:
- devtype = "unknown?";
- break;
- };
-
- switch (info->state) {
- case CUBEB_DEVICE_STATE_DISABLED:
- devstate = "disabled";
- break;
- case CUBEB_DEVICE_STATE_UNPLUGGED:
- devstate = "unplugged";
- break;
- case CUBEB_DEVICE_STATE_ENABLED:
- devstate = "enabled";
- break;
- default:
- devstate = "unknown?";
- break;
- };
-
- switch (info->default_format) {
- case CUBEB_DEVICE_FMT_S16LE:
- devdeffmt = "S16LE";
- break;
- case CUBEB_DEVICE_FMT_S16BE:
- devdeffmt = "S16BE";
- break;
- case CUBEB_DEVICE_FMT_F32LE:
- devdeffmt = "F32LE";
- break;
- case CUBEB_DEVICE_FMT_F32BE:
- devdeffmt = "F32BE";
- break;
- default:
- devdeffmt = "unknown?";
- break;
- };
-
- if (info->format & CUBEB_DEVICE_FMT_S16LE)
- strcat(devfmts, " S16LE");
- if (info->format & CUBEB_DEVICE_FMT_S16BE)
- strcat(devfmts, " S16BE");
- if (info->format & CUBEB_DEVICE_FMT_F32LE)
- strcat(devfmts, " F32LE");
- if (info->format & CUBEB_DEVICE_FMT_F32BE)
- strcat(devfmts, " F32BE");
-
- fprintf(f,
- "dev: \"%s\"%s\n"
- "\tName: \"%s\"\n"
- "\tGroup: \"%s\"\n"
- "\tVendor: \"%s\"\n"
- "\tType: %s\n"
- "\tState: %s\n"
- "\tCh: %u\n"
- "\tFormat: %s (0x%x) (default: %s)\n"
- "\tRate: %u - %u (default: %u)\n"
- "\tLatency: lo %ums, hi %ums\n",
- info->device_id, info->preferred ? " (PREFERRED)" : "",
- info->friendly_name, info->group_id, info->vendor_name,
- devtype, devstate, info->max_channels,
- (devfmts[0] == ' ') ? &devfmts[1] : devfmts,
- (unsigned int)info->format, devdeffmt,
- info->min_rate, info->max_rate, info->default_rate,
- info->latency_lo_ms, info->latency_hi_ms);
-}
-
-static void
-print_device_collection(cubeb_device_collection * collection, FILE * f)
-{
- uint32_t i;
-
- for (i = 0; i < collection->count; i++)
- print_device_info(collection->device[i], f);
-}
-
-static int
-run_enumerate_devices(void)
-{
- int r = CUBEB_OK;
- cubeb * ctx = NULL;
- cubeb_device_collection * collection = NULL;
-
- r = cubeb_init(&ctx, "Cubeb audio test");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- return r;
- }
-
- fprintf(stdout, "Enumerating input devices for backend %s\n",
- cubeb_get_backend_id(ctx));
-
- r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error enumerating devices %d\n", r);
- goto cleanup;
- }
-
- fprintf(stdout, "Found %u input devices\n", collection->count);
- print_device_collection(collection, stdout);
- cubeb_device_collection_destroy(collection);
-
- fprintf(stdout, "Enumerating output devices for backend %s\n",
- cubeb_get_backend_id(ctx));
-
- r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error enumerating devices %d\n", r);
- goto cleanup;
- }
-
- fprintf(stdout, "Found %u output devices\n", collection->count);
- print_device_collection(collection, stdout);
- cubeb_device_collection_destroy(collection);
-
-cleanup:
- cubeb_destroy(ctx);
- return r;
-}
-
-int main(int argc, char *argv[])
-{
- int ret;
-
- ret = run_enumerate_devices();
-
- return ret;
-}
diff --git a/media/libcubeb/tests/test_duplex.cpp b/media/libcubeb/tests/test_duplex.cpp
deleted file mode 100644
index c5d02d782c..0000000000
--- a/media/libcubeb/tests/test_duplex.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright © 2016 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-/* libcubeb api/function test. Loops input back to output and check audio
- * is flowing. */
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-#define _XOPEN_SOURCE 600
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-#include <assert.h>
-
-#include "cubeb/cubeb.h"
-#include "common.h"
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
-
-#define SAMPLE_FREQUENCY 48000
-#if (defined(_WIN32) || defined(__WIN32__))
-#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
-#define SILENT_SAMPLE 0.0f
-#else
-#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
-#define SILENT_SAMPLE 0
-#endif
-
-struct user_state
-{
- bool seen_noise;
-};
-
-
-
-long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
-{
- user_state * u = reinterpret_cast<user_state*>(user);
-#if (defined(_WIN32) || defined(__WIN32__))
- float *ib = (float *)inputbuffer;
- float *ob = (float *)outputbuffer;
-#else
- short *ib = (short *)inputbuffer;
- short *ob = (short *)outputbuffer;
-#endif
- bool seen_noise = false;
-
- if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
- return CUBEB_ERROR;
- }
-
- // Loop back: upmix the single input channel to the two output channels,
- // checking if there is noise in the process.
- long output_index = 0;
- for (long i = 0; i < nframes; i++) {
- if (ib[i] != SILENT_SAMPLE) {
- seen_noise = true;
- }
- ob[output_index] = ob[output_index + 1] = ib[i];
- output_index += 2;
- }
-
- u->seen_noise |= seen_noise;
-
- return nframes;
-}
-
-void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
-{
- if (stream == NULL)
- return;
-
- switch (state) {
- case CUBEB_STATE_STARTED:
- printf("stream started\n"); break;
- case CUBEB_STATE_STOPPED:
- printf("stream stopped\n"); break;
- case CUBEB_STATE_DRAINED:
- printf("stream drained\n"); break;
- default:
- printf("unknown stream state %d\n", state);
- }
-
- return;
-}
-
-int main(int /*argc*/, char * /*argv*/[])
-{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_duplex");
-#endif
-
- cubeb *ctx;
- cubeb_stream *stream;
- cubeb_stream_params input_params;
- cubeb_stream_params output_params;
- int r;
- user_state stream_state = { false };
- uint32_t latency_frames = 0;
-
- r = cubeb_init(&ctx, "Cubeb duplex example");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- return r;
- }
-
- /* This test needs an available input device, skip it if this host does not
- * have one. */
- if (!has_available_input_device(ctx)) {
- return 0;
- }
-
- /* typical user-case: mono input, stereo output, low latency. */
- input_params.format = STREAM_FORMAT;
- input_params.rate = 48000;
- input_params.channels = 1;
- output_params.format = STREAM_FORMAT;
- output_params.rate = 48000;
- output_params.channels = 2;
-
- r = cubeb_get_min_latency(ctx, output_params, &latency_frames);
-
- if (r != CUBEB_OK) {
- fprintf(stderr, "Could not get minimal latency\n");
- return r;
- }
-
- r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
- NULL, &input_params, NULL, &output_params,
- latency_frames, data_cb, state_cb, &stream_state);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb stream\n");
- return r;
- }
-
- cubeb_stream_start(stream);
- delay(500);
- cubeb_stream_stop(stream);
-
- cubeb_stream_destroy(stream);
- cubeb_destroy(ctx);
-
- assert(stream_state.seen_noise);
-
- return CUBEB_OK;
-}
diff --git a/media/libcubeb/tests/test_latency.cpp b/media/libcubeb/tests/test_latency.cpp
deleted file mode 100644
index 0586b1a367..0000000000
--- a/media/libcubeb/tests/test_latency.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-#include <stdlib.h>
-#include "cubeb/cubeb.h"
-#include <assert.h>
-#include <stdio.h>
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
-
-#define LOG(msg) fprintf(stderr, "%s\n", msg);
-
-int main(int /*argc*/, char * /*argv*/[])
-{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_latency");
-#endif
-
- cubeb * ctx = NULL;
- int r;
- uint32_t max_channels;
- uint32_t preferred_rate;
- uint32_t latency_frames;
-
- LOG("latency_test start");
- r = cubeb_init(&ctx, "Cubeb audio test");
- assert(r == CUBEB_OK && "Cubeb init failed.");
- LOG("cubeb_init ok");
-
- r = cubeb_get_max_channel_count(ctx, &max_channels);
- assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
- if (r == CUBEB_OK) {
- assert(max_channels > 0 && "Invalid max channel count.");
- LOG("cubeb_get_max_channel_count ok");
- }
-
- r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate);
- assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
- if (r == CUBEB_OK) {
- assert(preferred_rate > 0 && "Invalid preferred sample rate.");
- LOG("cubeb_get_preferred_sample_rate ok");
- }
-
- cubeb_stream_params params = {
- CUBEB_SAMPLE_FLOAT32NE,
- preferred_rate,
- max_channels
- };
- r = cubeb_get_min_latency(ctx, params, &latency_frames);
- assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
- if (r == CUBEB_OK) {
- assert(latency_frames > 0 && "Invalid minimal latency.");
- LOG("cubeb_get_min_latency ok");
- }
-
- cubeb_destroy(ctx);
- LOG("cubeb_destroy ok");
- return EXIT_SUCCESS;
-}
diff --git a/media/libcubeb/tests/test_record.cpp b/media/libcubeb/tests/test_record.cpp
deleted file mode 100644
index 681e1641e2..0000000000
--- a/media/libcubeb/tests/test_record.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright © 2016 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-
-/* libcubeb api/function test. Record the mic and check there is sound. */
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-#define _XOPEN_SOURCE 600
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-#include <assert.h>
-
-#include "cubeb/cubeb.h"
-#include "common.h"
-#ifdef CUBEB_GECKO_BUILD
-#include "TestHarness.h"
-#endif
-
-#define SAMPLE_FREQUENCY 48000
-#if (defined(_WIN32) || defined(__WIN32__))
-#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
-#else
-#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
-#endif
-
-struct user_state
-{
- bool seen_noise;
-};
-
-long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
-{
- user_state * u = reinterpret_cast<user_state*>(user);
-#if STREAM_FORMAT != CUBEB_SAMPLE_FLOAT32LE
- short *b = (short *)inputbuffer;
-#else
- float *b = (float *)inputbuffer;
-#endif
-
- if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
- return CUBEB_ERROR;
- }
-
- bool seen_noise = false;
- for (long i = 0; i < nframes; i++) {
- if (b[i] != 0.0) {
- seen_noise = true;
- }
- }
-
- u->seen_noise |= seen_noise;
-
- return nframes;
-}
-
-void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
-{
- if (stream == NULL)
- return;
-
- switch (state) {
- case CUBEB_STATE_STARTED:
- printf("stream started\n"); break;
- case CUBEB_STATE_STOPPED:
- printf("stream stopped\n"); break;
- case CUBEB_STATE_DRAINED:
- printf("stream drained\n"); break;
- default:
- printf("unknown stream state %d\n", state);
- }
-
- return;
-}
-
-int main(int /*argc*/, char * /*argv*/[])
-{
-#ifdef CUBEB_GECKO_BUILD
- ScopedXPCOM xpcom("test_record");
-#endif
-
- cubeb *ctx;
- cubeb_stream *stream;
- cubeb_stream_params params;
- int r;
- user_state stream_state = { false };
-
- r = cubeb_init(&ctx, "Cubeb record example");
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb library\n");
- return r;
- }
-
- /* This test needs an available input device, skip it if this host does not
- * have one. */
- if (!has_available_input_device(ctx)) {
- return 0;
- }
-
- params.format = STREAM_FORMAT;
- params.rate = SAMPLE_FREQUENCY;
- params.channels = 1;
-
- r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, &params, NULL, nullptr,
- 4096, data_cb, state_cb, &stream_state);
- if (r != CUBEB_OK) {
- fprintf(stderr, "Error initializing cubeb stream\n");
- return r;
- }
-
- cubeb_stream_start(stream);
- delay(500);
- cubeb_stream_stop(stream);
-
- cubeb_stream_destroy(stream);
- cubeb_destroy(ctx);
-
- assert(stream_state.seen_noise);
-
- return CUBEB_OK;
-}
diff --git a/media/libcubeb/tests/test_resampler.cpp b/media/libcubeb/tests/test_resampler.cpp
deleted file mode 100644
index 7e62a35721..0000000000
--- a/media/libcubeb/tests/test_resampler.cpp
+++ /dev/null
@@ -1,554 +0,0 @@
-/*
- * Copyright © 2016 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
-#ifndef NOMINMAX
-#define NOMINMAX
-#endif // NOMINMAX
-
-#ifdef NDEBUG
-#undef NDEBUG
-#endif
-#include "cubeb_resampler_internal.h"
-#include <assert.h>
-#include <stdio.h>
-#include <algorithm>
-#include <iostream>
-
-/* Windows cmath USE_MATH_DEFINE thing... */
-const float PI = 3.14159265359f;
-
-/* Testing all sample rates is very long, so if THOROUGH_TESTING is not defined,
- * only part of the test suite is ran. */
-#ifdef THOROUGH_TESTING
-/* Some standard sample rates we're testing with. */
-const uint32_t sample_rates[] = {
- 8000,
- 16000,
- 32000,
- 44100,
- 48000,
- 88200,
- 96000,
- 192000
-};
-/* The maximum number of channels we're resampling. */
-const uint32_t max_channels = 2;
-/* The minimum an maximum number of milliseconds we're resampling for. This is
- * used to simulate the fact that the audio stream is resampled in chunks,
- * because audio is delivered using callbacks. */
-const uint32_t min_chunks = 10; /* ms */
-const uint32_t max_chunks = 30; /* ms */
-const uint32_t chunk_increment = 1;
-
-#else
-
-const uint32_t sample_rates[] = {
- 8000,
- 44100,
- 48000,
-};
-const uint32_t max_channels = 2;
-const uint32_t min_chunks = 10; /* ms */
-const uint32_t max_chunks = 30; /* ms */
-const uint32_t chunk_increment = 10;
-#endif
-
-#define DUMP_ARRAYS
-#ifdef DUMP_ARRAYS
-/**
- * Files produced by dump(...) can be converted to .wave files using:
- *
- * sox -c <channel_count> -r <rate> -e float -b 32 file.raw file.wav
- *
- * for floating-point audio, or:
- *
- * sox -c <channel_count> -r <rate> -e unsigned -b 16 file.raw file.wav
- *
- * for 16bit integer audio.
- */
-
-/* Use the correct implementation of fopen, depending on the platform. */
-void fopen_portable(FILE ** f, const char * name, const char * mode)
-{
-#ifdef WIN32
- fopen_s(f, name, mode);
-#else
- *f = fopen(name, mode);
-#endif
-}
-
-template<typename T>
-void dump(const char * name, T * frames, size_t count)
-{
- FILE * file;
- fopen_portable(&file, name, "wb");
-
- if (!file) {
- fprintf(stderr, "error opening %s\n", name);
- return;
- }
-
- if (count != fwrite(frames, sizeof(T), count, file)) {
- fprintf(stderr, "error writing to %s\n", name);
- }
- fclose(file);
-}
-#else
-template<typename T>
-void dump(const char * name, T * frames, size_t count)
-{ }
-#endif
-
-// The more the ratio is far from 1, the more we accept a big error.
-float epsilon_tweak_ratio(float ratio)
-{
- return ratio >= 1 ? ratio : 1 / ratio;
-}
-
-// Epsilon values for comparing resampled data to expected data.
-// The bigger the resampling ratio is, the more lax we are about errors.
-template<typename T>
-T epsilon(float ratio);
-
-template<>
-float epsilon(float ratio) {
- return 0.08f * epsilon_tweak_ratio(ratio);
-}
-
-template<>
-int16_t epsilon(float ratio) {
- return static_cast<int16_t>(10 * epsilon_tweak_ratio(ratio));
-}
-
-void test_delay_lines(uint32_t delay_frames, uint32_t channels, uint32_t chunk_ms)
-{
- const size_t length_s = 2;
- const size_t rate = 44100;
- const size_t length_frames = rate * length_s;
- delay_line<float> delay(delay_frames, channels);
- auto_array<float> input;
- auto_array<float> output;
- uint32_t chunk_length = channels * chunk_ms * rate / 1000;
- uint32_t output_offset = 0;
- uint32_t channel = 0;
-
- /** Generate diracs every 100 frames, and check they are delayed. */
- input.push_silence(length_frames * channels);
- for (uint32_t i = 0; i < input.length() - 1; i+=100) {
- input.data()[i + channel] = 0.5;
- channel = (channel + 1) % channels;
- }
- dump("input.raw", input.data(), input.length());
- while(input.length()) {
- uint32_t to_pop = std::min<uint32_t>(input.length(), chunk_length * channels);
- float * in = delay.input_buffer(to_pop / channels);
- input.pop(in, to_pop);
- delay.written(to_pop / channels);
- output.push_silence(to_pop);
- delay.output(output.data() + output_offset, to_pop / channels);
- output_offset += to_pop;
- }
-
- // Check the diracs have been shifted by `delay_frames` frames.
- for (uint32_t i = 0; i < output.length() - delay_frames * channels + 1; i+=100) {
- assert(output.data()[i + channel + delay_frames * channels] == 0.5);
- channel = (channel + 1) % channels;
- }
-
- dump("output.raw", output.data(), output.length());
-}
-/**
- * This takes sine waves with a certain `channels` count, `source_rate`, and
- * resample them, by chunk of `chunk_duration` milliseconds, to `target_rate`.
- * Then a sample-wise comparison is performed against a sine wave generated at
- * the correct rate.
- */
-template<typename T>
-void test_resampler_one_way(uint32_t channels, uint32_t source_rate, uint32_t target_rate, float chunk_duration)
-{
- size_t chunk_duration_in_source_frames = static_cast<uint32_t>(ceil(chunk_duration * source_rate / 1000.));
- float resampling_ratio = static_cast<float>(source_rate) / target_rate;
- cubeb_resampler_speex_one_way<T> resampler(channels, source_rate, target_rate, 3);
- auto_array<T> source(channels * source_rate * 10);
- auto_array<T> destination(channels * target_rate * 10);
- auto_array<T> expected(channels * target_rate * 10);
- uint32_t phase_index = 0;
- uint32_t offset = 0;
- const uint32_t buf_len = 2; /* seconds */
-
- // generate a sine wave in each channel, at the source sample rate
- source.push_silence(channels * source_rate * buf_len);
- while(offset != source.length()) {
- float p = phase_index++ / static_cast<float>(source_rate);
- for (uint32_t j = 0; j < channels; j++) {
- source.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
- }
- }
-
- dump("input.raw", source.data(), source.length());
-
- expected.push_silence(channels * target_rate * buf_len);
- // generate a sine wave in each channel, at the target sample rate.
- // Insert silent samples at the beginning to account for the resampler latency.
- offset = resampler.latency() * channels;
- for (uint32_t i = 0; i < offset; i++) {
- expected.data()[i] = 0.0f;
- }
- phase_index = 0;
- while (offset != expected.length()) {
- float p = phase_index++ / static_cast<float>(target_rate);
- for (uint32_t j = 0; j < channels; j++) {
- expected.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
- }
- }
-
- dump("expected.raw", expected.data(), expected.length());
-
- // resample by chunk
- uint32_t write_offset = 0;
- destination.push_silence(channels * target_rate * buf_len);
- while (write_offset < destination.length())
- {
- size_t output_frames = static_cast<uint32_t>(floor(chunk_duration_in_source_frames / resampling_ratio));
- uint32_t input_frames = resampler.input_needed_for_output(output_frames);
- resampler.input(source.data(), input_frames);
- source.pop(nullptr, input_frames * channels);
- resampler.output(destination.data() + write_offset,
- std::min(output_frames, (destination.length() - write_offset) / channels));
- write_offset += output_frames * channels;
- }
-
- dump("output.raw", destination.data(), expected.length());
-
- // compare, taking the latency into account
- bool fuzzy_equal = true;
- for (uint32_t i = resampler.latency() + 1; i < expected.length(); i++) {
- float diff = fabs(expected.data()[i] - destination.data()[i]);
- if (diff > epsilon<T>(resampling_ratio)) {
- fprintf(stderr, "divergence at %d: %f %f (delta %f)\n", i, expected.data()[i], destination.data()[i], diff);
- fuzzy_equal = false;
- }
- }
- assert(fuzzy_equal);
-}
-
-template<typename T>
-cubeb_sample_format cubeb_format();
-
-template<>
-cubeb_sample_format cubeb_format<float>()
-{
- return CUBEB_SAMPLE_FLOAT32NE;
-}
-
-template<>
-cubeb_sample_format cubeb_format<short>()
-{
- return CUBEB_SAMPLE_S16NE;
-}
-
-struct osc_state {
- osc_state()
- : input_phase_index(0)
- , output_phase_index(0)
- , output_offset(0)
- , input_channels(0)
- , output_channels(0)
- {}
- uint32_t input_phase_index;
- uint32_t max_output_phase_index;
- uint32_t output_phase_index;
- uint32_t output_offset;
- uint32_t input_channels;
- uint32_t output_channels;
- uint32_t output_rate;
- uint32_t target_rate;
- auto_array<float> input;
- auto_array<float> output;
-};
-
-uint32_t fill_with_sine(float * buf, uint32_t rate, uint32_t channels,
- uint32_t frames, uint32_t initial_phase)
-{
- uint32_t offset = 0;
- for (uint32_t i = 0; i < frames; i++) {
- float p = initial_phase++ / static_cast<float>(rate);
- for (uint32_t j = 0; j < channels; j++) {
- buf[offset++] = 0.5 * sin(440. * 2 * PI * p);
- }
- }
- return initial_phase;
-}
-
-long data_cb(cubeb_stream * /*stm*/, void * user_ptr,
- const void * input_buffer, void * output_buffer, long frame_count)
-{
- osc_state * state = reinterpret_cast<osc_state*>(user_ptr);
- const float * in = reinterpret_cast<const float*>(input_buffer);
- float * out = reinterpret_cast<float*>(output_buffer);
-
-
- state->input.push(in, frame_count * state->input_channels);
-
- /* Check how much output frames we need to write */
- uint32_t remaining = state->max_output_phase_index - state->output_phase_index;
- uint32_t to_write = std::min<uint32_t>(remaining, frame_count);
- state->output_phase_index = fill_with_sine(out,
- state->target_rate,
- state->output_channels,
- to_write,
- state->output_phase_index);
-
- return to_write;
-}
-
-template<typename T>
-bool array_fuzzy_equal(const auto_array<T>& lhs, const auto_array<T>& rhs, T epsi)
-{
- uint32_t len = std::min(lhs.length(), rhs.length());
-
- for (uint32_t i = 0; i < len; i++) {
- if (fabs(lhs.at(i) - rhs.at(i)) > epsi) {
- std::cout << "not fuzzy equal at index: " << i
- << " lhs: " << lhs.at(i) << " rhs: " << rhs.at(i)
- << " delta: " << fabs(lhs.at(i) - rhs.at(i))
- << " epsilon: "<< epsi << std::endl;
- return false;
- }
- }
- return true;
-}
-
-template<typename T>
-void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels,
- uint32_t input_rate, uint32_t output_rate,
- uint32_t target_rate, float chunk_duration)
-{
- cubeb_stream_params input_params;
- cubeb_stream_params output_params;
- osc_state state;
-
- input_params.format = output_params.format = cubeb_format<T>();
- state.input_channels = input_params.channels = input_channels;
- state.output_channels = output_params.channels = output_channels;
- input_params.rate = input_rate;
- state.output_rate = output_params.rate = output_rate;
- state.target_rate = target_rate;
- long got;
-
- cubeb_resampler * resampler =
- cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate,
- data_cb, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP);
-
- long latency = cubeb_resampler_latency(resampler);
-
- const uint32_t duration_s = 2;
- int32_t duration_frames = duration_s * target_rate;
- uint32_t input_array_frame_count = ceil(chunk_duration * input_rate / 1000) + ceilf(static_cast<float>(input_rate) / target_rate) * 2;
- uint32_t output_array_frame_count = chunk_duration * output_rate / 1000;
- auto_array<float> input_buffer(input_channels * input_array_frame_count);
- auto_array<float> output_buffer(output_channels * output_array_frame_count);
- auto_array<float> expected_resampled_input(input_channels * duration_frames);
- auto_array<float> expected_resampled_output(output_channels * output_rate * duration_s);
-
- state.max_output_phase_index = duration_s * target_rate;
-
- expected_resampled_input.push_silence(input_channels * duration_frames);
- expected_resampled_output.push_silence(output_channels * output_rate * duration_s);
-
- /* expected output is a 440Hz sine wave at 16kHz */
- fill_with_sine(expected_resampled_input.data() + latency,
- target_rate, input_channels, duration_frames - latency, 0);
- /* expected output is a 440Hz sine wave at 32kHz */
- fill_with_sine(expected_resampled_output.data() + latency,
- output_rate, output_channels, output_rate * duration_s - latency, 0);
-
-
- while (state.output_phase_index != state.max_output_phase_index) {
- uint32_t leftover_samples = input_buffer.length() * input_channels;
- input_buffer.reserve(input_array_frame_count);
- state.input_phase_index = fill_with_sine(input_buffer.data() + leftover_samples,
- input_rate,
- input_channels,
- input_array_frame_count - leftover_samples,
- state.input_phase_index);
- long input_consumed = input_array_frame_count;
- input_buffer.set_length(input_array_frame_count);
-
- got = cubeb_resampler_fill(resampler,
- input_buffer.data(), &input_consumed,
- output_buffer.data(), output_array_frame_count);
-
- /* handle leftover input */
- if (input_array_frame_count != static_cast<uint32_t>(input_consumed)) {
- input_buffer.pop(nullptr, input_consumed * input_channels);
- } else {
- input_buffer.clear();
- }
-
- state.output.push(output_buffer.data(), got * state.output_channels);
- }
-
- dump("input_expected.raw", expected_resampled_input.data(), expected_resampled_input.length());
- dump("output_expected.raw", expected_resampled_output.data(), expected_resampled_output.length());
- dump("input.raw", state.input.data(), state.input.length());
- dump("output.raw", state.output.data(), state.output.length());
-
- assert(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
- assert(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate)));
-
- cubeb_resampler_destroy(resampler);
-}
-
-#define array_size(x) (sizeof(x) / sizeof(x[0]))
-
-void test_resamplers_one_way()
-{
- /* Test one way resamplers */
- for (uint32_t channels = 1; channels <= max_channels; channels++) {
- for (uint32_t source_rate = 0; source_rate < array_size(sample_rates); source_rate++) {
- for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
- for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
- printf("one_way: channels: %d, source_rate: %d, dest_rate: %d, chunk_duration: %d\n",
- channels, sample_rates[source_rate], sample_rates[dest_rate], chunk_duration);
- test_resampler_one_way<float>(channels, sample_rates[source_rate],
- sample_rates[dest_rate], chunk_duration);
- }
- }
- }
- }
-}
-
-void test_resamplers_duplex()
-{
- /* Test duplex resamplers */
- for (uint32_t input_channels = 1; input_channels <= max_channels; input_channels++) {
- for (uint32_t output_channels = 1; output_channels <= max_channels; output_channels++) {
- for (uint32_t source_rate_input = 0; source_rate_input < array_size(sample_rates); source_rate_input++) {
- for (uint32_t source_rate_output = 0; source_rate_output < array_size(sample_rates); source_rate_output++) {
- for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
- for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
- printf("input channels:%d output_channels:%d input_rate:%d "
- "output_rate:%d target_rate:%d chunk_ms:%d\n",
- input_channels, output_channels,
- sample_rates[source_rate_input],
- sample_rates[source_rate_output],
- sample_rates[dest_rate],
- chunk_duration);
- test_resampler_duplex<float>(input_channels, output_channels,
- sample_rates[source_rate_input],
- sample_rates[source_rate_output],
- sample_rates[dest_rate],
- chunk_duration);
- }
- }
- }
- }
- }
- }
-}
-
-void test_delay_line()
-{
- for (uint32_t channel = 1; channel <= 2; channel++) {
- for (uint32_t delay_frames = 4; delay_frames <= 40; delay_frames+=chunk_increment) {
- for (uint32_t chunk_size = 10; chunk_size <= 30; chunk_size++) {
- printf("channel: %d, delay_frames: %d, chunk_size: %d\n",
- channel, delay_frames, chunk_size);
- test_delay_lines(delay_frames, channel, chunk_size);
- }
- }
- }
-}
-
-long test_output_only_noop_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/,
- const void * input_buffer,
- void * output_buffer, long frame_count)
-{
- assert(output_buffer);
- assert(!input_buffer);
- return frame_count;
-}
-
-void test_output_only_noop()
-{
- cubeb_stream_params output_params;
- int target_rate;
-
- output_params.rate = 44100;
- output_params.channels = 1;
- output_params.format = CUBEB_SAMPLE_FLOAT32NE;
- target_rate = output_params.rate;
-
- cubeb_resampler * resampler =
- cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
- test_output_only_noop_data_cb, nullptr,
- CUBEB_RESAMPLER_QUALITY_VOIP);
-
- const long out_frames = 128;
- float out_buffer[out_frames];
- long got;
-
- got = cubeb_resampler_fill(resampler, nullptr, nullptr,
- out_buffer, out_frames);
-
- assert(got == out_frames);
-
- cubeb_resampler_destroy(resampler);
-}
-
-long test_drain_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/,
- const void * input_buffer,
- void * output_buffer, long frame_count)
-{
- assert(output_buffer);
- assert(!input_buffer);
- return frame_count - 10;
-}
-
-void test_resampler_drain()
-{
- cubeb_stream_params output_params;
- int target_rate;
-
- output_params.rate = 44100;
- output_params.channels = 1;
- output_params.format = CUBEB_SAMPLE_FLOAT32NE;
- target_rate = 48000;
-
- cubeb_resampler * resampler =
- cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
- test_drain_data_cb, nullptr,
- CUBEB_RESAMPLER_QUALITY_VOIP);
-
- const long out_frames = 128;
- float out_buffer[out_frames];
- long got;
-
- do {
- got = cubeb_resampler_fill(resampler, nullptr, nullptr,
- out_buffer, out_frames);
- } while (got == out_frames);
-
- /* If the above is not an infinite loop, the drain was a success, just mark
- * this test as such. */
- assert(true);
-
- cubeb_resampler_destroy(resampler);
-}
-
-int main()
-{
- test_resamplers_one_way();
- test_delay_line();
- // This is disabled because the latency estimation in the resampler code is
- // slightly off so we can generate expected vectors.
- // test_resamplers_duplex();
- test_output_only_noop();
- test_resampler_drain();
-
- return 0;
-}
diff --git a/media/libcubeb/tests/test_utils.cpp b/media/libcubeb/tests/test_utils.cpp
deleted file mode 100644
index f52cd31963..0000000000
--- a/media/libcubeb/tests/test_utils.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-#include <cassert>
-#include "cubeb_utils.h"
-
-int test_auto_array()
-{
- auto_array<uint32_t> array;
- auto_array<uint32_t> array2(10);
- uint32_t a[10];
-
- assert(array2.length() == 0);
- assert(array2.capacity() == 10);
-
-
- for (uint32_t i = 0; i < 10; i++) {
- a[i] = i;
- }
-
- assert(array.capacity() == 0);
- assert(array.length() == 0);
-
- array.push(a, 10);
-
- assert(!array.reserve(9));
-
- for (uint32_t i = 0; i < 10; i++) {
- assert(array.data()[i] == i);
- }
-
- assert(array.capacity() == 10);
- assert(array.length() == 10);
-
- uint32_t b[10];
-
- array.pop(b, 5);
-
- assert(array.capacity() == 10);
- assert(array.length() == 5);
- for (uint32_t i = 0; i < 5; i++) {
- assert(b[i] == i);
- assert(array.data()[i] == 5 + i);
- }
- uint32_t* bb = b + 5;
- array.pop(bb, 5);
-
- assert(array.capacity() == 10);
- assert(array.length() == 0);
- for (uint32_t i = 0; i < 5; i++) {
- assert(bb[i] == 5 + i);
- }
-
- assert(!array.pop(nullptr, 1));
-
- array.push(a, 10);
- array.push(a, 10);
-
- for (uint32_t j = 0; j < 2; j++) {
- for (uint32_t i = 0; i < 10; i++) {
- assert(array.data()[10 * j + i] == i);
- }
- }
- assert(array.length() == 20);
- assert(array.capacity() == 20);
- array.pop(nullptr, 5);
-
- for (uint32_t i = 0; i < 5; i++) {
- assert(array.data()[i] == 5 + i);
- }
-
- assert(array.length() == 15);
- assert(array.capacity() == 20);
-
- return 0;
-}
-
-
-int main()
-{
- test_auto_array();
- return 0;
-}
diff --git a/media/libcubeb/unresampled-frames.patch b/media/libcubeb/unresampled-frames.patch
deleted file mode 100644
index 714f3d4bae..0000000000
--- a/media/libcubeb/unresampled-frames.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From 46d12e9ae6fa9c233bc32812b13185ee7df8d3fd Mon Sep 17 00:00:00 2001
-From: Paul Adenot <paul@paul.cx>
-Date: Thu, 10 Nov 2016 06:20:16 +0100
-Subject: [PATCH] Prevent underflowing the number of input frames needed in
- input when resampling. (#188)
-
----
- src/cubeb_resampler_internal.h | 12 +++++++++---
- 1 file changed, 9 insertions(+), 3 deletions(-)
-
-diff --git a/src/cubeb_resampler_internal.h b/src/cubeb_resampler_internal.h
-index e165cc2..3c37a04 100644
---- a/src/cubeb_resampler_internal.h
-+++ b/src/cubeb_resampler_internal.h
-@@ -263,9 +263,15 @@ public:
- * number of output frames will be exactly equal. */
- uint32_t input_needed_for_output(uint32_t output_frame_count)
- {
-- return (uint32_t)ceilf((output_frame_count - samples_to_frames(resampling_out_buffer.length()))
-- * resampling_ratio);
--
-+ int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length());
-+ int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length());
-+ float input_frames_needed =
-+ (output_frame_count - unresampled_frames_left) * resampling_ratio
-+ - resampled_frames_left;
-+ if (input_frames_needed < 0) {
-+ return 0;
-+ }
-+ return (uint32_t)ceilf(input_frames_needed);
- }
-
- /** Returns a pointer to the input buffer, that contains empty space for at
---
-2.7.4
-
diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh
index 235b963e24..4229524b5e 100755
--- a/media/libcubeb/update.sh
+++ b/media/libcubeb/update.sh
@@ -5,41 +5,55 @@ cp $1/AUTHORS .
cp $1/LICENSE .
cp $1/README.md .
cp $1/include/cubeb/cubeb.h include
-cp $1/src/android/audiotrack_definitions.h src/android
cp $1/src/android/sles_definitions.h src/android
cp $1/src/cubeb-internal.h src
cp $1/src/cubeb-speex-resampler.h src
cp $1/src/cubeb.c src
+cp $1/src/cubeb_aaudio.cpp src
cp $1/src/cubeb_alsa.c src
-cp $1/src/cubeb_log.h src
-cp $1/src/cubeb_audiotrack.c src
+cp $1/src/cubeb_array_queue.h src
+cp $1/src/cubeb_assert.h src
cp $1/src/cubeb_audiounit.cpp src
-cp $1/src/cubeb_osx_run_loop.h src
cp $1/src/cubeb_jack.cpp src
+cp $1/src/cubeb_log.cpp src
+cp $1/src/cubeb_log.h src
+cp $1/src/cubeb_mixer.cpp src
+cp $1/src/cubeb_mixer.h src
cp $1/src/cubeb_opensl.c src
-cp $1/src/cubeb_panner.cpp src
-cp $1/src/cubeb_panner.h src
-cp $1/src/cubeb_pulse.c src
+cp $1/src/cubeb_android.h src
+cp $1/src/cubeb-jni.cpp src
+cp $1/src/cubeb-jni.h src
+cp $1/src/android/cubeb-output-latency.h src/android
+cp $1/src/android/cubeb_media_library.h src/android
+cp $1/src/cubeb_oss.c src
+cp $1/src/cubeb_osx_run_loop.h src
cp $1/src/cubeb_resampler.cpp src
cp $1/src/cubeb_resampler.h src
cp $1/src/cubeb_resampler_internal.h src
cp $1/src/cubeb_ring_array.h src
+cp $1/src/cubeb_ringbuffer.h src
cp $1/src/cubeb_sndio.c src
+cp $1/src/cubeb_strings.c src
+cp $1/src/cubeb_strings.h src
+cp $1/src/cubeb_sun.c src
cp $1/src/cubeb_utils.h src
+cp $1/src/cubeb_utils.cpp src
cp $1/src/cubeb_utils_unix.h src
cp $1/src/cubeb_utils_win.h src
cp $1/src/cubeb_wasapi.cpp src
-cp $1/src/cubeb_winmm.c src
-cp $1/test/common.h tests/common.h
-cp $1/test/test_audio.cpp tests/test_audio.cpp
-#cp $1/test/test_devices.c tests/test_devices.cpp
-cp $1/test/test_duplex.cpp tests/test_duplex.cpp
-cp $1/test/test_latency.cpp tests/test_latency.cpp
-cp $1/test/test_record.cpp tests/test_record.cpp
-cp $1/test/test_resampler.cpp tests/test_resampler.cpp
-cp $1/test/test_sanity.cpp tests/test_sanity.cpp
-cp $1/test/test_tone.cpp tests/test_tone.cpp
-cp $1/test/test_utils.cpp tests/test_utils.cpp
+cp $1/test/common.h gtest
+cp $1/test/test_audio.cpp gtest
+cp $1/test/test_devices.cpp gtest
+cp $1/test/test_duplex.cpp gtest
+cp $1/test/test_latency.cpp gtest
+cp $1/test/test_loopback.cpp gtest
+cp $1/test/test_overload_callback.cpp gtest
+cp $1/test/test_record.cpp gtest
+cp $1/test/test_resampler.cpp gtest
+cp $1/test/test_ring_array.cpp gtest
+cp $1/test/test_sanity.cpp gtest
+cp $1/test/test_tone.cpp gtest
+cp $1/test/test_utils.cpp gtest
if [ -d $1/.git ]; then
rev=$(cd $1 && git rev-parse --verify HEAD)
@@ -57,33 +71,3 @@ if [ -n "$rev" ]; then
else
echo "Remember to update README_MOZILLA with the version details."
fi
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./unresampled-frames.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./bug1302231_emergency_bailout.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./osx-linearize-operations.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./prevent-double-free.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./bug1292803_pulse_assert.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./uplift-wasapi-part-to-beta.patch
-
-echo "Applying a patch on top of $version"
-patch -p3 < ./fix-crashes.patch
-
-echo "Applying a patch on top of $version"
-patch -p3 < ./uplift-part-of-f07ee6d-esr52.patch
-
-echo "Applying a patch on top of $version"
-patch -p3 < ./uplift-system-listener-patch.patch
-
-echo "Applying a patch on top of $version"
-patch -p1 < ./uplift-patch-7a4c711.patch
diff --git a/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch b/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch
deleted file mode 100644
index 0eb1aca82b..0000000000
--- a/media/libcubeb/uplift-part-of-f07ee6d-esr52.patch
+++ /dev/null
@@ -1,167 +0,0 @@
-# HG changeset patch
-# User Alex Chronopoulos <achronop@gmail.com>
-# Parent 00c051cd38c7a6cb3178fd0890d52056f83abfdc
-Bug 1345049 - Uplift part of cubeb upstream f07ee6d to esr52. r=padenot. a=xxxxxx
-
-diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp
---- a/media/libcubeb/src/cubeb_audiounit.cpp
-+++ b/media/libcubeb/src/cubeb_audiounit.cpp
-@@ -590,33 +590,43 @@ audiounit_get_input_device_id(AudioDevic
- device_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- return CUBEB_OK;
- }
-
-+static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
-+static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
-+
- static int
- audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
- {
-+ auto_lock context_lock(stm->context->mutex);
- if (is_started) {
- audiounit_stream_stop_internal(stm);
- }
-
- {
- auto_lock lock(stm->mutex);
-+ float volume = 0.0;
-+ int vol_rv = audiounit_stream_get_volume(stm, &volume);
-
- audiounit_close_stream(stm);
-
- if (audiounit_setup_stream(stm) != CUBEB_OK) {
- LOG("(%p) Stream reinit failed.", stm);
- return CUBEB_ERROR;
- }
-
-+ if (vol_rv == CUBEB_OK) {
-+ audiounit_stream_set_volume(stm, volume);
-+ }
-+
- // Reset input frames to force new stream pre-buffer
- // silence if needed, check `is_extra_input_needed()`
- stm->frames_read = 0;
-
- // If the stream was running, start it again.
- if (is_started) {
- audiounit_stream_start_internal(stm);
- }
-@@ -1007,20 +1017,22 @@ audiounit_get_preferred_sample_rate(cube
- static OSStatus audiounit_remove_device_listener(cubeb * context);
-
- static void
- audiounit_destroy(cubeb * ctx)
- {
- // Disabling this assert for bug 1083664 -- we seem to leak a stream
- // assert(ctx->active_streams == 0);
-
-- /* Unregister the callback if necessary. */
-- if(ctx->collection_changed_callback) {
-+ {
- auto_lock lock(ctx->mutex);
-- audiounit_remove_device_listener(ctx);
-+ /* Unregister the callback if necessary. */
-+ if(ctx->collection_changed_callback) {
-+ audiounit_remove_device_listener(ctx);
-+ }
- }
-
- ctx->~cubeb();
- free(ctx);
- }
-
- static void audiounit_stream_destroy(cubeb_stream * stm);
-
-@@ -1861,17 +1873,17 @@ audiounit_close_stream(cubeb_stream *stm
- cubeb_resampler_destroy(stm->resampler);
- }
-
- static void
- audiounit_stream_destroy(cubeb_stream * stm)
- {
- stm->shutdown = true;
-
-- auto_lock context_locl(stm->context->mutex);
-+ auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- {
- auto_lock lock(stm->mutex);
- audiounit_close_stream(stm);
- }
-
- #if !TARGET_OS_IPHONE
-@@ -1905,17 +1917,17 @@ audiounit_stream_start_internal(cubeb_st
- }
-
- static int
- audiounit_stream_start(cubeb_stream * stm)
- {
- stm->shutdown = false;
- stm->draining = false;
-
-- auto_lock context_locl(stm->context->mutex);
-+ auto_lock context_lock(stm->context->mutex);
- audiounit_stream_start_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
-
- LOG("Cubeb stream (%p) started successfully.", stm);
- return CUBEB_OK;
- }
-
-@@ -1933,17 +1945,17 @@ audiounit_stream_stop_internal(cubeb_str
- }
- }
-
- static int
- audiounit_stream_stop(cubeb_stream * stm)
- {
- stm->shutdown = true;
-
-- auto_lock context_locl(stm->context->mutex);
-+ auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-
- LOG("Cubeb stream (%p) stopped successfully.", stm);
- return CUBEB_OK;
- }
-
-@@ -2030,16 +2042,31 @@ audiounit_stream_get_latency(cubeb_strea
- }
-
- *latency = stm->hw_latency_frames + stm->current_latency_frames;
-
- return CUBEB_OK;
- #endif
- }
-
-+static int
-+audiounit_stream_get_volume(cubeb_stream * stm, float * volume)
-+{
-+ assert(stm->output_unit);
-+ OSStatus r = AudioUnitGetParameter(stm->output_unit,
-+ kHALOutputParam_Volume,
-+ kAudioUnitScope_Global,
-+ 0, volume);
-+ if (r != noErr) {
-+ LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r);
-+ return CUBEB_ERROR;
-+ }
-+ return CUBEB_OK;
-+}
-+
- int audiounit_stream_set_volume(cubeb_stream * stm, float volume)
- {
- OSStatus r;
-
- r = AudioUnitSetParameter(stm->output_unit,
- kHALOutputParam_Volume,
- kAudioUnitScope_Global,
- 0, volume, 0);
diff --git a/media/libcubeb/uplift-patch-7a4c711.patch b/media/libcubeb/uplift-patch-7a4c711.patch
deleted file mode 100644
index 188bdf8b2f..0000000000
--- a/media/libcubeb/uplift-patch-7a4c711.patch
+++ /dev/null
@@ -1,69 +0,0 @@
-From 7a4c711d6e998b451326a0a87dd2e9dab5a257ef Mon Sep 17 00:00:00 2001
-From: Alex Chronopoulos <achronop@gmail.com>
-Date: Mon, 15 May 2017 16:47:26 +0300
-Subject: [PATCH] audiounit: synchronize destroy stream and reinit (Bug
- 1361657)
-
----
- src/cubeb_audiounit.cpp | 22 +++++++++++++++-------
- 1 file changed, 15 insertions(+), 7 deletions(-)
-
-diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp
-index 8aa40d54..331bc735 100644
---- a/src/cubeb_audiounit.cpp
-+++ b/src/cubeb_audiounit.cpp
-@@ -603,6 +603,7 @@ audiounit_get_input_device_id(AudioDeviceID * device_id)
-
- static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
- static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
-+static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
-
- static int
- audiounit_reinit_stream(cubeb_stream * stm)
-@@ -612,6 +613,11 @@ audiounit_reinit_stream(cubeb_stream * stm)
- audiounit_stream_stop_internal(stm);
- }
-
-+ int r = audiounit_uninstall_device_changed_callback(stm);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not uninstall the device changed callback", stm);
-+ }
-+
- {
- auto_lock lock(stm->mutex);
- float volume = 0.0;
-@@ -2516,11 +2522,6 @@ audiounit_close_stream(cubeb_stream *stm)
- {
- stm->mutex.assert_current_thread_owns();
-
-- int r = audiounit_uninstall_device_changed_callback(stm);
-- if (r != CUBEB_OK) {
-- LOG("(%p) Could not uninstall the device changed callback", stm);
-- }
--
- if (stm->input_unit) {
- AudioUnitUninitialize(stm->input_unit);
- AudioComponentInstanceDispose(stm->input_unit);
-@@ -2554,13 +2555,20 @@ audiounit_stream_destroy(cubeb_stream * stm)
- LOG("(%p) Could not uninstall the device changed callback", stm);
- }
-
-+ r = audiounit_uninstall_device_changed_callback(stm);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not uninstall the device changed callback", stm);
-+ }
-+
- auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
-- {
-+ // Execute close in serial queue to avoid collision
-+ // with reinit when un/plug devices
-+ dispatch_sync(stm->context->serial_queue, ^() {
- auto_lock lock(stm->mutex);
- audiounit_close_stream(stm);
-- }
-+ });
-
- assert(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
diff --git a/media/libcubeb/uplift-system-listener-patch.patch b/media/libcubeb/uplift-system-listener-patch.patch
deleted file mode 100644
index 5064d7fb35..0000000000
--- a/media/libcubeb/uplift-system-listener-patch.patch
+++ /dev/null
@@ -1,402 +0,0 @@
-diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp
---- a/media/libcubeb/src/cubeb_audiounit.cpp
-+++ b/media/libcubeb/src/cubeb_audiounit.cpp
-@@ -594,20 +594,20 @@ audiounit_get_input_device_id(AudioDevic
-
- return CUBEB_OK;
- }
-
- static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
- static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
-
- static int
--audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
-+audiounit_reinit_stream(cubeb_stream * stm)
- {
- auto_lock context_lock(stm->context->mutex);
-- if (is_started) {
-+ if (!stm->shutdown) {
- audiounit_stream_stop_internal(stm);
- }
-
- {
- auto_lock lock(stm->mutex);
- float volume = 0.0;
- int vol_rv = audiounit_stream_get_volume(stm, &volume);
-
-@@ -622,32 +622,30 @@ audiounit_reinit_stream(cubeb_stream * s
- audiounit_stream_set_volume(stm, volume);
- }
-
- // Reset input frames to force new stream pre-buffer
- // silence if needed, check `is_extra_input_needed()`
- stm->frames_read = 0;
-
- // If the stream was running, start it again.
-- if (is_started) {
-+ if (!stm->shutdown) {
- audiounit_stream_start_internal(stm);
- }
- }
- return CUBEB_OK;
- }
-
- static OSStatus
- audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
- const AudioObjectPropertyAddress * addresses,
- void * user)
- {
- cubeb_stream * stm = (cubeb_stream*) user;
- stm->switching_device = true;
-- // Note if the stream was running or not
-- bool was_running = !stm->shutdown;
-
- LOG("(%p) Audio device changed, %d events.", stm, address_count);
- for (UInt32 i = 0; i < address_count; i++) {
- switch(addresses[i].mSelector) {
- case kAudioHardwarePropertyDefaultOutputDevice: {
- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i);
- // Allow restart to choose the new default
- stm->output_device = nullptr;
-@@ -666,19 +664,20 @@ audiounit_property_listener_callback(Aud
- if (stm->is_default_input) {
- LOG("It's the default input device, ignore the event");
- return noErr;
- }
- // Allow restart to choose the new default. Event register only for input.
- stm->input_device = nullptr;
- }
- break;
-- case kAudioDevicePropertyDataSource:
-- LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i);
-- break;
-+ case kAudioDevicePropertyDataSource: {
-+ LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i);
-+ return noErr;
-+ }
- }
- }
-
- for (UInt32 i = 0; i < address_count; i++) {
- switch(addresses[i].mSelector) {
- case kAudioHardwarePropertyDefaultOutputDevice:
- case kAudioHardwarePropertyDefaultInputDevice:
- case kAudioDevicePropertyDeviceIsAlive:
-@@ -691,17 +690,17 @@ audiounit_property_listener_callback(Aud
- break;
- }
- }
- }
-
- // Use a new thread, through the queue, to avoid deadlock when calling
- // Get/SetProperties method from inside notify callback
- dispatch_async(stm->context->serial_queue, ^() {
-- if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) {
-+ if (audiounit_reinit_stream(stm) != CUBEB_OK) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
- LOG("(%p) Could not reopen the stream after switching.", stm);
- }
- stm->switching_device = false;
- });
-
- return noErr;
- }
-@@ -752,27 +751,16 @@ audiounit_install_device_changed_callbac
- }
-
- r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource", r);
- return CUBEB_ERROR;
- }
--
-- /* This event will notify us when the default audio device changes,
-- * for example when the user plugs in a USB headset and the system chooses it
-- * automatically as the default, or when another device is chosen in the
-- * dropdown list. */
-- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
-- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice", r);
-- return CUBEB_ERROR;
-- }
- }
-
- if (stm->input_unit) {
- /* This event will notify us when the data source on the input device changes. */
- AudioDeviceID input_dev_id;
- r = audiounit_get_input_device_id(&input_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
-@@ -780,78 +768,112 @@ audiounit_install_device_changed_callbac
-
- r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource", r);
- return CUBEB_ERROR;
- }
-
-- /* This event will notify us when the default input device changes. */
-- r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
-- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-- if (r != noErr) {
-- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice", r);
-- return CUBEB_ERROR;
-- }
--
- /* Event to notify when the input is going away. */
- AudioDeviceID dev = stm->input_device ? reinterpret_cast<intptr_t>(stm->input_device) :
- audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
- r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive,
- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
- if (r != noErr) {
- PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r);
- return CUBEB_ERROR;
- }
- }
-
- return CUBEB_OK;
- }
-
- static int
-+audiounit_install_system_changed_callback(cubeb_stream * stm)
-+{
-+ OSStatus r;
-+
-+ if (stm->output_unit) {
-+ /* This event will notify us when the default audio device changes,
-+ * for example when the user plugs in a USB headset and the system chooses it
-+ * automatically as the default, or when another device is chosen in the
-+ * dropdown list. */
-+ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
-+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-+ if (r != noErr) {
-+ LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r);
-+ return CUBEB_ERROR;
-+ }
-+ }
-+
-+ if (stm->input_unit) {
-+ /* This event will notify us when the default input device changes. */
-+ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
-+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-+ if (r != noErr) {
-+ LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r);
-+ return CUBEB_ERROR;
-+ }
-+ }
-+
-+ return CUBEB_OK;
-+}
-+
-+static int
- audiounit_uninstall_device_changed_callback(cubeb_stream * stm)
- {
- OSStatus r;
-
- if (stm->output_unit) {
- AudioDeviceID output_dev_id;
- r = audiounit_get_output_device_id(&output_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
--
-- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
-- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-- if (r != noErr) {
-- return CUBEB_ERROR;
-- }
- }
-
- if (stm->input_unit) {
- AudioDeviceID input_dev_id;
- r = audiounit_get_input_device_id(&input_dev_id);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
-
- r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
- kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
--
-+ }
-+ return CUBEB_OK;
-+}
-+
-+static int
-+audiounit_uninstall_system_changed_callback(cubeb_stream * stm)
-+{
-+ OSStatus r;
-+
-+ if (stm->output_unit) {
-+ r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
-+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-+ if (r != noErr) {
-+ return CUBEB_ERROR;
-+ }
-+ }
-+
-+ if (stm->input_unit) {
- r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
-- kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-+ kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
- if (r != noErr) {
- return CUBEB_ERROR;
- }
- }
- return CUBEB_OK;
- }
-
- /* Get the acceptable buffer size (in frames) that this device can work with. */
-@@ -1764,16 +1786,22 @@ audiounit_setup_stream(cubeb_stream * st
-
- if (stm->input_unit && stm->output_unit) {
- // According to the I/O hardware rate it is expected a specific pattern of callbacks
- // for example is input is 44100 and output is 48000 we expected no more than 2
- // out callback in a row.
- stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate);
- }
-
-+ r = audiounit_install_device_changed_callback(stm);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not install the device change callback.", stm);
-+ return r;
-+ }
-+
- return CUBEB_OK;
- }
-
- static int
- audiounit_stream_init(cubeb * context,
- cubeb_stream ** stream,
- char const * /* stream_name */,
- cubeb_devid input_device,
-@@ -1838,31 +1866,37 @@ audiounit_stream_init(cubeb * context,
- }
-
- if (r != CUBEB_OK) {
- LOG("(%p) Could not setup the audiounit stream.", stm);
- audiounit_stream_destroy(stm);
- return r;
- }
-
-- r = audiounit_install_device_changed_callback(stm);
-+ r = audiounit_install_system_changed_callback(stm);
- if (r != CUBEB_OK) {
- LOG("(%p) Could not install the device change callback.", stm);
- return r;
- }
-
- *stream = stm;
- LOG("Cubeb stream (%p) init successful.", stm);
- return CUBEB_OK;
- }
-
- static void
- audiounit_close_stream(cubeb_stream *stm)
- {
- stm->mutex.assert_current_thread_owns();
-+
-+ int r = audiounit_uninstall_device_changed_callback(stm);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not uninstall the device changed callback", stm);
-+ }
-+
- if (stm->input_unit) {
- AudioUnitUninitialize(stm->input_unit);
- AudioComponentInstanceDispose(stm->input_unit);
- }
-
- audiounit_destroy_input_linear_buffer(stm);
-
- if (stm->output_unit) {
-@@ -1873,31 +1907,29 @@ audiounit_close_stream(cubeb_stream *stm
- cubeb_resampler_destroy(stm->resampler);
- }
-
- static void
- audiounit_stream_destroy(cubeb_stream * stm)
- {
- stm->shutdown = true;
-
-+ int r = audiounit_uninstall_system_changed_callback(stm);
-+ if (r != CUBEB_OK) {
-+ LOG("(%p) Could not uninstall the device changed callback", stm);
-+ }
-+
- auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- {
- auto_lock lock(stm->mutex);
- audiounit_close_stream(stm);
- }
-
--#if !TARGET_OS_IPHONE
-- int r = audiounit_uninstall_device_changed_callback(stm);
-- if (r != CUBEB_OK) {
-- LOG("(%p) Could not uninstall the device changed callback", stm);
-- }
--#endif
--
- assert(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
-
- LOG("Cubeb stream (%p) destroyed successful.", stm);
-
- stm->~cubeb_stream();
- free(stm);
- }
-@@ -1914,20 +1946,20 @@ audiounit_stream_start_internal(cubeb_st
- r = AudioOutputUnitStart(stm->output_unit);
- assert(r == 0);
- }
- }
-
- static int
- audiounit_stream_start(cubeb_stream * stm)
- {
-+ auto_lock context_lock(stm->context->mutex);
- stm->shutdown = false;
- stm->draining = false;
-
-- auto_lock context_lock(stm->context->mutex);
- audiounit_stream_start_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
-
- LOG("Cubeb stream (%p) started successfully.", stm);
- return CUBEB_OK;
- }
-
-@@ -1943,19 +1975,19 @@ audiounit_stream_stop_internal(cubeb_str
- r = AudioOutputUnitStop(stm->output_unit);
- assert(r == 0);
- }
- }
-
- static int
- audiounit_stream_stop(cubeb_stream * stm)
- {
-+ auto_lock context_lock(stm->context->mutex);
- stm->shutdown = true;
-
-- auto_lock context_lock(stm->context->mutex);
- audiounit_stream_stop_internal(stm);
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-
- LOG("Cubeb stream (%p) stopped successfully.", stm);
- return CUBEB_OK;
- }
-
diff --git a/media/libcubeb/uplift-wasapi-part-to-beta.patch b/media/libcubeb/uplift-wasapi-part-to-beta.patch
deleted file mode 100644
index 90e827830e..0000000000
--- a/media/libcubeb/uplift-wasapi-part-to-beta.patch
+++ /dev/null
@@ -1,118 +0,0 @@
-# HG changeset patch
-# User Alex Chronopoulos <achronop@gmail.com>
-# Parent b7bb31e5a851d6f8e142c39dc077e3774719eced
-Bug 1342363 - Uplift wasapi fixes in Beta. r?kinetik
-
-diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp
---- a/media/libcubeb/src/cubeb_wasapi.cpp
-+++ b/media/libcubeb/src/cubeb_wasapi.cpp
-@@ -807,16 +807,20 @@ wasapi_stream_render_loop(LPVOID stream)
- maybe WebRTC. */
- mmcss_handle =
- stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index);
- if (!mmcss_handle) {
- /* This is not fatal, but we might glitch under heavy load. */
- LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError());
- }
-
-+ // This has already been nulled out, simply exit.
-+ if (!emergency_bailout) {
-+ is_playing = false;
-+ }
-
- /* WaitForMultipleObjects timeout can trigger in cases where we don't want to
- treat it as a timeout, such as across a system sleep/wake cycle. Trigger
- the timeout error handling only when the timeout_limit is reached, which is
- reset on each successful loop. */
- unsigned timeout_count = 0;
- const unsigned timeout_limit = 5;
- while (is_playing) {
-@@ -1158,22 +1162,26 @@ bool stop_and_join_render_thread(cubeb_s
-
- /* Wait five seconds for the rendering thread to return. It's supposed to
- * check its event loop very often, five seconds is rather conservative. */
- DWORD r = WaitForSingleObject(stm->thread, 5000);
- if (r == WAIT_TIMEOUT) {
- /* Something weird happened, leak the thread and continue the shutdown
- * process. */
- *(stm->emergency_bailout) = true;
-+ // We give the ownership to the rendering thread.
-+ stm->emergency_bailout = nullptr;
- LOG("Destroy WaitForSingleObject on thread timed out,"
- " leaking the thread: %d", GetLastError());
- rv = false;
- }
- if (r == WAIT_FAILED) {
- *(stm->emergency_bailout) = true;
-+ // We give the ownership to the rendering thread.
-+ stm->emergency_bailout = nullptr;
- LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError());
- rv = false;
- }
-
-
- // Only attempts to close and null out the thread and event if the
- // WaitForSingleObject above succeeded, so that calling this function again
- // attemps to clean up the thread and event each time.
-@@ -1798,19 +1806,16 @@ void wasapi_stream_destroy(cubeb_stream
- XASSERT(stm);
-
- // Only free stm->emergency_bailout if we could not join the thread.
- // If we could not join the thread, stm->emergency_bailout is true
- // and is still alive until the thread wakes up and exits cleanly.
- if (stop_and_join_render_thread(stm)) {
- delete stm->emergency_bailout.load();
- stm->emergency_bailout = nullptr;
-- } else {
-- // If we're leaking, it must be that this is true.
-- assert(*(stm->emergency_bailout));
- }
-
- unregister_notification_client(stm);
-
- SafeRelease(stm->reconfigure_event);
- SafeRelease(stm->refill_event);
- SafeRelease(stm->input_available_event);
-
-@@ -1865,21 +1870,21 @@ int stream_start_one_side(cubeb_stream *
- return CUBEB_ERROR;
- }
-
- return CUBEB_OK;
- }
-
- int wasapi_stream_start(cubeb_stream * stm)
- {
-+ auto_lock lock(stm->stream_reset_lock);
-+
- XASSERT(stm && !stm->thread && !stm->shutdown_event);
- XASSERT(stm->output_client || stm->input_client);
-
-- auto_lock lock(stm->stream_reset_lock);
--
- stm->emergency_bailout = new std::atomic<bool>(false);
-
- if (stm->output_client) {
- int rv = stream_start_one_side(stm, OUTPUT);
- if (rv != CUBEB_OK) {
- return rv;
- }
- }
-@@ -1932,16 +1937,17 @@ int wasapi_stream_stop(cubeb_stream * st
- }
- }
-
-
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
- }
-
- if (stop_and_join_render_thread(stm)) {
-+ // This is null if we've given the pointer to the other thread
- if (stm->emergency_bailout.load()) {
- delete stm->emergency_bailout.load();
- stm->emergency_bailout = nullptr;
- }
- }
-
- return CUBEB_OK;
- }
diff --git a/mfbt/LinkedList.h b/mfbt/LinkedList.h
index a099d7597a..9d04e64c77 100644
--- a/mfbt/LinkedList.h
+++ b/mfbt/LinkedList.h
@@ -327,7 +327,8 @@ private:
void setNextUnsafe(RawType aElem)
{
LinkedListElement *listElem = static_cast<LinkedListElement*>(aElem);
- MOZ_ASSERT(!listElem->isInList());
+ if (listElem->isInList())
+ return;
listElem->mNext = this->mNext;
listElem->mPrev = this;
@@ -344,7 +345,8 @@ private:
void setPreviousUnsafe(RawType aElem)
{
LinkedListElement<T>* listElem = static_cast<LinkedListElement<T>*>(aElem);
- MOZ_ASSERT(!listElem->isInList());
+ if (listElem->isInList())
+ return;
listElem->mNext = this;
listElem->mPrev = this->mPrev;
diff --git a/mfbt/SegmentedVector.h b/mfbt/SegmentedVector.h
index 6d255102fa..6e4dc2a452 100644
--- a/mfbt/SegmentedVector.h
+++ b/mfbt/SegmentedVector.h
@@ -120,9 +120,9 @@ class SegmentedVector : private AllocPolicy
? (IdealSegmentSize - kSingleElementSegmentSize) / sizeof(T) + 1
: 1;
+public:
typedef SegmentImpl<kSegmentCapacity> Segment;
-public:
// The |aIdealSegmentSize| is only for sanity checking. If it's specified, we
// check that the actual segment size is as close as possible to it. This
// serves as a sanity check for SegmentedVectorCapacity's capacity
@@ -259,7 +259,6 @@ public:
// than we want to pop.
MOZ_ASSERT(last);
MOZ_ASSERT(last == mSegments.getLast());
- MOZ_ASSERT(aNumElements != 0);
MOZ_ASSERT(aNumElements < last->Length());
for (uint32_t i = 0; i < aNumElements; ++i) {
last->PopLast();
@@ -274,6 +273,10 @@ public:
// f(elem);
// }
//
+ // Note, adding new entries to the SegmentedVector while using iterators
+ // is supported, but removing is not!
+ // If an iterator has entered Done() state, adding more entries to the
+ // vector doesn't affect it.
class IterImpl
{
friend class SegmentedVector;
@@ -281,10 +284,14 @@ public:
Segment* mSegment;
size_t mIndex;
- explicit IterImpl(SegmentedVector* aVector)
- : mSegment(aVector->mSegments.getFirst())
- , mIndex(0)
- {}
+ explicit IterImpl(SegmentedVector* aVector, bool aFromFirst)
+ : mSegment(aFromFirst ? aVector->mSegments.getFirst() :
+ aVector->mSegments.getLast())
+ , mIndex(aFromFirst ? 0 :
+ (mSegment ? mSegment->Length() - 1 : 0))
+ {
+ MOZ_ASSERT_IF(mSegment, mSegment->Length() > 0);
+ }
public:
bool Done() const { return !mSegment; }
@@ -310,10 +317,23 @@ public:
mIndex = 0;
}
}
- };
- IterImpl Iter() { return IterImpl(this); }
+ void Prev()
+ {
+ MOZ_ASSERT(!Done());
+ if (mIndex == 0) {
+ mSegment = mSegment->getPrevious();
+ if (mSegment) {
+ mIndex = mSegment->Length() - 1;
+ }
+ } else {
+ --mIndex;
+ }
+ }
+ };
+ IterImpl Iter() { return IterImpl(this, true); }
+ IterImpl IterFromLast() { return IterImpl(this, false); }
// Measure the memory consumption of the vector excluding |this|. Note that
// it only measures the vector itself. If the vector elements contain
// pointers to other memory blocks, those blocks must be measured separately
diff --git a/netwerk/cache2/CacheIndex.cpp b/netwerk/cache2/CacheIndex.cpp
index 2c6451d72d..0c46647c63 100644
--- a/netwerk/cache2/CacheIndex.cpp
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -41,14 +41,17 @@ namespace {
class FrecencyComparator
{
public:
- bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const {
+ bool Equals(const RefPtr<CacheIndexRecordWrapper>& a,
+ const RefPtr<CacheIndexRecordWrapper>& b) const {
if (!a || !b) {
return false;
}
- return a->mFrecency == b->mFrecency;
+ return a->Get()->mFrecency == b->Get()->mFrecency;
}
- bool LessThan(CacheIndexRecord* a, CacheIndexRecord* b) const {
+
+ bool LessThan(const RefPtr<CacheIndexRecordWrapper>& a,
+ const RefPtr<CacheIndexRecordWrapper>& b) const {
// Removed (=null) entries must be at the end of the array.
if (!a) {
return false;
@@ -58,14 +61,14 @@ public:
}
// Place entries with frecency 0 at the end of the non-removed entries.
- if (a->mFrecency == 0) {
+ if (a->Get()->mFrecency == 0) {
return false;
}
- if (b->mFrecency == 0) {
+ if (b->Get()->mFrecency == 0) {
return true;
}
- return a->mFrecency < b->mFrecency;
+ return a->Get()->mFrecency < b->Get()->mFrecency;
}
};
@@ -75,31 +78,28 @@ public:
* This helper class is responsible for keeping CacheIndex::mIndexStats and
* CacheIndex::mFrecencyArray up to date.
*/
-class CacheIndexEntryAutoManage
+class MOZ_RAII CacheIndexEntryAutoManage
{
public:
- CacheIndexEntryAutoManage(const SHA1Sum::Hash *aHash, CacheIndex *aIndex)
+ CacheIndexEntryAutoManage(const SHA1Sum::Hash *aHash, CacheIndex *aIndex,
+ const StaticMutexAutoLock& aProofOfLock)
: mIndex(aIndex)
- , mOldRecord(nullptr)
, mOldFrecency(0)
, mDoNotSearchInIndex(false)
, mDoNotSearchInUpdates(false)
+ , mProofOfLock(aProofOfLock)
{
- CacheIndex::sLock.AssertCurrentThreadOwns();
-
mHash = aHash;
const CacheIndexEntry *entry = FindEntry();
mIndex->mIndexStats.BeforeChange(entry);
if (entry && entry->IsInitialized() && !entry->IsRemoved()) {
mOldRecord = entry->mRec;
- mOldFrecency = entry->mRec->mFrecency;
+ mOldFrecency = entry->mRec->Get()->mFrecency;
}
}
~CacheIndexEntryAutoManage()
{
- CacheIndex::sLock.AssertCurrentThreadOwns();
-
const CacheIndexEntry *entry = FindEntry();
mIndex->mIndexStats.AfterChange(entry);
if (!entry || !entry->IsInitialized() || entry->IsRemoved()) {
@@ -107,28 +107,28 @@ public:
}
if (entry && !mOldRecord) {
- mIndex->mFrecencyArray.AppendRecord(entry->mRec);
- mIndex->AddRecordToIterators(entry->mRec);
+ mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
+ mIndex->AddRecordToIterators(entry->mRec, mProofOfLock);
} else if (!entry && mOldRecord) {
- mIndex->mFrecencyArray.RemoveRecord(mOldRecord);
- mIndex->RemoveRecordFromIterators(mOldRecord);
+ mIndex->mFrecencyArray.RemoveRecord(mOldRecord, mProofOfLock);
+ mIndex->RemoveRecordFromIterators(mOldRecord, mProofOfLock);
} else if (entry && mOldRecord) {
if (entry->mRec != mOldRecord) {
// record has a different address, we have to replace it
- mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec);
+ mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec, mProofOfLock);
- if (entry->mRec->mFrecency == mOldFrecency) {
+ if (entry->mRec->Get()->mFrecency == mOldFrecency) {
// If frecency hasn't changed simply replace the pointer
- mIndex->mFrecencyArray.ReplaceRecord(mOldRecord, entry->mRec);
+ mIndex->mFrecencyArray.ReplaceRecord(mOldRecord, entry->mRec, mProofOfLock);
} else {
// Remove old pointer and insert the new one at the end of the array
- mIndex->mFrecencyArray.RemoveRecord(mOldRecord);
- mIndex->mFrecencyArray.AppendRecord(entry->mRec);
+ mIndex->mFrecencyArray.RemoveRecord(mOldRecord, mProofOfLock);
+ mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
}
- } else if (entry->mRec->mFrecency != mOldFrecency) {
+ } else if (entry->mRec->Get()->mFrecency != mOldFrecency) {
// Move the element at the end of the array
- mIndex->mFrecencyArray.RemoveRecord(entry->mRec);
- mIndex->mFrecencyArray.AppendRecord(entry->mRec);
+ mIndex->mFrecencyArray.RemoveRecord(entry->mRec, mProofOfLock);
+ mIndex->mFrecencyArray.AppendRecord(entry->mRec, mProofOfLock);
}
} else {
// both entries were removed or not initialized, do nothing
@@ -169,12 +169,13 @@ private:
return entry;
}
- const SHA1Sum::Hash *mHash;
+ const SHA1Sum::Hash* mHash;
RefPtr<CacheIndex> mIndex;
- CacheIndexRecord *mOldRecord;
- uint32_t mOldFrecency;
- bool mDoNotSearchInIndex;
- bool mDoNotSearchInUpdates;
+ RefPtr<CacheIndexRecordWrapper> mOldRecord;
+ uint32_t mOldFrecency;
+ bool mDoNotSearchInIndex;
+ bool mDoNotSearchInUpdates;
+ const StaticMutexAutoLock& mProofOfLock;
};
class FileOpenHelper : public CacheFileIOListener
@@ -236,7 +237,7 @@ NS_IMETHODIMP FileOpenHelper::OnFileOpened(CacheFileHandle *aHandle,
return NS_OK;
}
- mIndex->OnFileOpenedInternal(this, aHandle, aResult);
+ mIndex->OnFileOpenedInternal(this, aHandle, aResult, lock);
return NS_OK;
}
@@ -306,7 +307,7 @@ CacheIndex::Init(nsIFile *aCacheDirectory)
RefPtr<CacheIndex> idx = new CacheIndex();
- nsresult rv = idx->InitInternal(aCacheDirectory);
+ nsresult rv = idx->InitInternal(aCacheDirectory, lock);
NS_ENSURE_SUCCESS(rv, rv);
gInstance = idx.forget();
@@ -314,7 +315,8 @@ CacheIndex::Init(nsIFile *aCacheDirectory)
}
nsresult
-CacheIndex::InitInternal(nsIFile *aCacheDirectory)
+CacheIndex::InitInternal(nsIFile *aCacheDirectory,
+ const StaticMutexAutoLock& aProofOfLock)
{
nsresult rv;
@@ -323,7 +325,7 @@ CacheIndex::InitInternal(nsIFile *aCacheDirectory)
mStartTime = TimeStamp::NowLoRes();
- ReadIndexFromDisk();
+ ReadIndexFromDisk(aProofOfLock);
return NS_OK;
}
@@ -402,17 +404,17 @@ CacheIndex::PreShutdownInternal()
switch (mState) {
case WRITING:
- FinishWrite(false);
+ FinishWrite(false, lock);
break;
case READY:
// nothing to do, write the journal in Shutdown()
break;
case READING:
- FinishRead(false);
+ FinishRead(false, lock);
break;
case BUILDING:
case UPDATING:
- FinishUpdate(false);
+ FinishUpdate(false, lock);
break;
default:
MOZ_ASSERT(false, "Implement me!");
@@ -447,7 +449,7 @@ CacheIndex::Shutdown()
MOZ_ASSERT(index->mShuttingDown);
EState oldState = index->mState;
- index->ChangeState(SHUTDOWN);
+ index->ChangeState(SHUTDOWN, lock);
if (oldState != READY) {
LOG(("CacheIndex::Shutdown() - Unexpected state. Did posting of "
@@ -456,7 +458,7 @@ CacheIndex::Shutdown()
switch (oldState) {
case WRITING:
- index->FinishWrite(false);
+ index->FinishWrite(false, lock);
MOZ_FALLTHROUGH;
case READY:
if (index->mIndexOnDiskIsValid && !index->mDontMarkIndexClean) {
@@ -468,11 +470,11 @@ CacheIndex::Shutdown()
}
break;
case READING:
- index->FinishRead(false);
+ index->FinishRead(false, lock);
break;
case BUILDING:
case UPDATING:
- index->FinishUpdate(false);
+ index->FinishUpdate(false, lock);
break;
default:
MOZ_ASSERT(false, "Unexpected state!");
@@ -512,7 +514,7 @@ CacheIndex::AddEntry(const SHA1Sum::Hash *aHash)
bool updateIfNonFreshEntriesExist = false;
{
- CacheIndexEntryAutoManage entryMng(aHash, index);
+ CacheIndexEntryAutoManage entryMng(aHash, index, lock);
CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
bool entryRemoved = entry && entry->IsRemoved();
@@ -593,8 +595,8 @@ CacheIndex::AddEntry(const SHA1Sum::Hash *aHash)
index->mIndexNeedsUpdate = true;
}
- index->StartUpdatingIndexIfNeeded();
- index->WriteIndexToDiskIfNeeded();
+ index->StartUpdatingIndexIfNeeded(lock);
+ index->WriteIndexToDiskIfNeeded(lock);
return NS_OK;
}
@@ -621,7 +623,7 @@ CacheIndex::EnsureEntryExists(const SHA1Sum::Hash *aHash)
}
{
- CacheIndexEntryAutoManage entryMng(aHash, index);
+ CacheIndexEntryAutoManage entryMng(aHash, index, lock);
CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
bool entryRemoved = entry && entry->IsRemoved();
@@ -699,8 +701,8 @@ CacheIndex::EnsureEntryExists(const SHA1Sum::Hash *aHash)
}
}
- index->StartUpdatingIndexIfNeeded();
- index->WriteIndexToDiskIfNeeded();
+ index->StartUpdatingIndexIfNeeded(lock);
+ index->WriteIndexToDiskIfNeeded(lock);
return NS_OK;
}
@@ -731,7 +733,7 @@ CacheIndex::InitEntry(const SHA1Sum::Hash *aHash,
}
{
- CacheIndexEntryAutoManage entryMng(aHash, index);
+ CacheIndexEntryAutoManage entryMng(aHash, index, lock);
CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
CacheIndexEntryUpdate *updated = nullptr;
@@ -812,8 +814,8 @@ CacheIndex::InitEntry(const SHA1Sum::Hash *aHash,
}
}
- index->StartUpdatingIndexIfNeeded();
- index->WriteIndexToDiskIfNeeded();
+ index->StartUpdatingIndexIfNeeded(lock);
+ index->WriteIndexToDiskIfNeeded(lock);
return NS_OK;
}
@@ -840,7 +842,7 @@ CacheIndex::RemoveEntry(const SHA1Sum::Hash *aHash)
}
{
- CacheIndexEntryAutoManage entryMng(aHash, index);
+ CacheIndexEntryAutoManage entryMng(aHash, index, lock);
CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
bool entryRemoved = entry && entry->IsRemoved();
@@ -907,8 +909,8 @@ CacheIndex::RemoveEntry(const SHA1Sum::Hash *aHash)
}
}
- index->StartUpdatingIndexIfNeeded();
- index->WriteIndexToDiskIfNeeded();
+ index->StartUpdatingIndexIfNeeded(lock);
+ index->WriteIndexToDiskIfNeeded(lock);
return NS_OK;
}
@@ -941,7 +943,7 @@ CacheIndex::UpdateEntry(const SHA1Sum::Hash *aHash,
}
{
- CacheIndexEntryAutoManage entryMng(aHash, index);
+ CacheIndexEntryAutoManage entryMng(aHash, index, lock);
CacheIndexEntry *entry = index->mIndex.GetEntry(*aHash);
@@ -1012,7 +1014,7 @@ CacheIndex::UpdateEntry(const SHA1Sum::Hash *aHash,
}
}
- index->WriteIndexToDiskIfNeeded();
+ index->WriteIndexToDiskIfNeeded(lock);
return NS_OK;
}
@@ -1061,17 +1063,17 @@ CacheIndex::RemoveAll()
switch (index->mState) {
case WRITING:
- index->FinishWrite(false);
+ index->FinishWrite(false, lock);
break;
case READY:
// nothing to do
break;
case READING:
- index->FinishRead(false);
+ index->FinishRead(false, lock);
break;
case BUILDING:
case UPDATING:
- index->FinishUpdate(false);
+ index->FinishUpdate(false, lock);
break;
default:
MOZ_ASSERT(false, "Unexpected state!");
@@ -1088,7 +1090,7 @@ CacheIndex::RemoveAll()
index->mIndexNeedsUpdate = false;
index->mIndexStats.Clear();
- index->mFrecencyArray.Clear();
+ index->mFrecencyArray.Clear(lock);
index->mIndex.Clear();
for (uint32_t i = 0; i < index->mIterators.Length(); ) {
@@ -1213,10 +1215,10 @@ CacheIndex::GetEntryForEviction(bool aIgnoreEmptyEntries, SHA1Sum::Hash *aHash,
uint32_t skipped = 0;
// find first non-forced valid and unpinned entry with the lowest frecency
- index->mFrecencyArray.SortIfNeeded();
+ index->mFrecencyArray.SortIfNeeded(lock);
for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
- CacheIndexRecord *rec = iter.Get();
+ CacheIndexRecord *rec = iter.Get()->Get();
memcpy(&hash, rec->mHash, sizeof(SHA1Sum::Hash));
@@ -1318,11 +1320,10 @@ CacheIndex::GetCacheStats(nsILoadContextInfo *aInfo, uint32_t *aSize, uint32_t *
*aCount = 0;
for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
- CacheIndexRecord *record = iter.Get();
- if (!CacheIndexEntry::RecordMatchesLoadContextInfo(record, aInfo))
+ if (aInfo && !CacheIndexEntry::RecordMatchesLoadContextInfo(iter.Get(), aInfo))
continue;
- *aSize += CacheIndexEntry::GetFileSize(record);
+ *aSize += CacheIndexEntry::GetFileSize(iter.Get()->Get());
++*aCount;
}
@@ -1374,7 +1375,7 @@ CacheIndex::AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserve
RefPtr<CacheIndex> index = gInstance;
if (index && index->mUpdateTimer) {
index->mUpdateTimer->Cancel();
- index->DelayedUpdateLocked();
+ index->DelayedUpdateLocked(lock);
}
}), CacheIOThread::INDEX);
}
@@ -1408,10 +1409,10 @@ CacheIndex::GetIterator(nsILoadContextInfo *aInfo, bool aAddNew,
idxIter = new CacheIndexIterator(index, aAddNew);
}
- index->mFrecencyArray.SortIfNeeded();
+ index->mFrecencyArray.SortIfNeeded(lock);
for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
- idxIter->AddRecord(iter.Get());
+ idxIter->AddRecord(iter.Get(), lock);
}
index->mIterators.AppendElement(idxIter);
@@ -1512,12 +1513,10 @@ CacheIndex::HasEntryChanged(CacheIndexEntry *aEntry,
}
void
-CacheIndex::ProcessPendingOperations()
+CacheIndex::ProcessPendingOperations(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::ProcessPendingOperations()"));
- sLock.AssertCurrentThreadOwns();
-
for (auto iter = mPendingUpdates.Iter(); !iter.Done(); iter.Next()) {
CacheIndexEntryUpdate* update = iter.Get();
@@ -1529,7 +1528,7 @@ CacheIndex::ProcessPendingOperations()
CacheIndexEntry* entry = mIndex.GetEntry(*update->Hash());
{
- CacheIndexEntryAutoManage emng(update->Hash(), this);
+ CacheIndexEntryAutoManage emng(update->Hash(), this, aProofOfLock);
emng.DoNotSearchInUpdates();
if (update->IsRemoved()) {
@@ -1571,7 +1570,7 @@ CacheIndex::ProcessPendingOperations()
}
bool
-CacheIndex::WriteIndexToDiskIfNeeded()
+CacheIndex::WriteIndexToDiskIfNeeded(const StaticMutexAutoLock& aProofOfLock)
{
if (mState != READY || mShuttingDown || mRWPending) {
return false;
@@ -1587,25 +1586,24 @@ CacheIndex::WriteIndexToDiskIfNeeded()
return false;
}
- WriteIndexToDisk();
+ WriteIndexToDisk(aProofOfLock);
return true;
}
void
-CacheIndex::WriteIndexToDisk()
+CacheIndex::WriteIndexToDisk(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::WriteIndexToDisk()"));
mIndexStats.Log();
nsresult rv;
- sLock.AssertCurrentThreadOwns();
MOZ_ASSERT(mState == READY);
MOZ_ASSERT(!mRWBuf);
MOZ_ASSERT(!mRWHash);
MOZ_ASSERT(!mRWPending);
- ChangeState(WRITING);
+ ChangeState(WRITING, aProofOfLock);
mProcessEntries = mIndexStats.ActiveEntriesCount();
@@ -1616,7 +1614,7 @@ CacheIndex::WriteIndexToDisk()
mIndexFileOpener);
if (NS_FAILED(rv)) {
LOG(("CacheIndex::WriteIndexToDisk() - Can't open file [rv=0x%08x]", rv));
- FinishWrite(false);
+ FinishWrite(false, aProofOfLock);
return;
}
@@ -1641,13 +1639,12 @@ CacheIndex::WriteIndexToDisk()
}
void
-CacheIndex::WriteRecords()
+CacheIndex::WriteRecords(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::WriteRecords()"));
nsresult rv;
- sLock.AssertCurrentThreadOwns();
MOZ_ASSERT(mState == WRITING);
MOZ_ASSERT(!mRWPending);
@@ -1725,7 +1722,7 @@ CacheIndex::WriteRecords()
if (NS_FAILED(rv)) {
LOG(("CacheIndex::WriteRecords() - CacheFileIOManager::Write() failed "
"synchronously [rv=0x%08x]", rv));
- FinishWrite(false);
+ FinishWrite(false, aProofOfLock);
} else {
mRWPending = true;
}
@@ -1734,14 +1731,12 @@ CacheIndex::WriteRecords()
}
void
-CacheIndex::FinishWrite(bool aSucceeded)
+CacheIndex::FinishWrite(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::FinishWrite() [succeeded=%d]", aSucceeded));
MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == WRITING);
- sLock.AssertCurrentThreadOwns();
-
// If there is write operation pending we must be cancelling writing of the
// index when shutting down or removing the whole index.
MOZ_ASSERT(!mRWPending || (!aSucceeded && (mShuttingDown || mRemovingAll)));
@@ -1759,7 +1754,7 @@ CacheIndex::FinishWrite(bool aSucceeded)
bool remove = false;
{
- CacheIndexEntryAutoManage emng(entry->Hash(), this);
+ CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
if (entry->IsRemoved()) {
emng.DoNotSearchInIndex();
@@ -1784,11 +1779,11 @@ CacheIndex::FinishWrite(bool aSucceeded)
}
}
- ProcessPendingOperations();
+ ProcessPendingOperations(aProofOfLock);
mIndexStats.Log();
if (mState == WRITING) {
- ChangeState(READY);
+ ChangeState(READY, aProofOfLock);
mLastDumpTime = TimeStamp::NowLoRes();
}
}
@@ -2011,16 +2006,15 @@ CacheIndex::WriteLogToDisk()
}
void
-CacheIndex::ReadIndexFromDisk()
+CacheIndex::ReadIndexFromDisk(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::ReadIndexFromDisk()"));
nsresult rv;
- sLock.AssertCurrentThreadOwns();
MOZ_ASSERT(mState == INITIAL);
- ChangeState(READING);
+ ChangeState(READING, aProofOfLock);
mIndexFileOpener = new FileOpenHelper(this);
rv = CacheFileIOManager::OpenFile(NS_LITERAL_CSTRING(INDEX_NAME),
@@ -2030,7 +2024,7 @@ CacheIndex::ReadIndexFromDisk()
if (NS_FAILED(rv)) {
LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
"failed [rv=0x%08x, file=%s]", rv, INDEX_NAME));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
return;
}
@@ -2042,7 +2036,7 @@ CacheIndex::ReadIndexFromDisk()
if (NS_FAILED(rv)) {
LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
"failed [rv=0x%08x, file=%s]", rv, JOURNAL_NAME));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
}
mTmpFileOpener = new FileOpenHelper(this);
@@ -2053,19 +2047,17 @@ CacheIndex::ReadIndexFromDisk()
if (NS_FAILED(rv)) {
LOG(("CacheIndex::ReadIndexFromDisk() - CacheFileIOManager::OpenFile() "
"failed [rv=0x%08x, file=%s]", rv, TEMP_INDEX_NAME));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
}
}
void
-CacheIndex::StartReadingIndex()
+CacheIndex::StartReadingIndex(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::StartReadingIndex()"));
nsresult rv;
- sLock.AssertCurrentThreadOwns();
-
MOZ_ASSERT(mIndexHandle);
MOZ_ASSERT(mState == READING);
MOZ_ASSERT(!mIndexOnDiskIsValid);
@@ -2079,7 +2071,7 @@ CacheIndex::StartReadingIndex()
if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
LOG(("CacheIndex::StartReadingIndex() - Index is corrupted"));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
return;
}
@@ -2094,21 +2086,19 @@ CacheIndex::StartReadingIndex()
if (NS_FAILED(rv)) {
LOG(("CacheIndex::StartReadingIndex() - CacheFileIOManager::Read() failed "
"synchronously [rv=0x%08x]", rv));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
} else {
mRWPending = true;
}
}
void
-CacheIndex::ParseRecords()
+CacheIndex::ParseRecords(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::ParseRecords()"));
nsresult rv;
- sLock.AssertCurrentThreadOwns();
-
MOZ_ASSERT(!mRWPending);
uint32_t entryCnt = (mIndexHandle->FileSize() - sizeof(CacheIndexHeader) -
@@ -2117,7 +2107,7 @@ CacheIndex::ParseRecords()
if (!mSkipEntries) {
if (NetworkEndian::readUint32(mRWBuf + pos) != kIndexVersion) {
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
return;
}
pos += sizeof(uint32_t);
@@ -2163,11 +2153,11 @@ CacheIndex::ParseRecords()
" whole index [dirty=%d, initialized=%d, fileEmpty=%d, fresh=%d, "
"removed=%d]", tmpEntry.IsDirty(), tmpEntry.IsInitialized(),
tmpEntry.IsFileEmpty(), tmpEntry.IsFresh(), tmpEntry.IsRemoved()));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
return;
}
- CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this);
+ CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this, aProofOfLock);
CacheIndexEntry *entry = mIndex.PutEntry(*tmpEntry.Hash());
*entry = tmpEntry;
@@ -2194,7 +2184,7 @@ CacheIndex::ParseRecords()
if (mRWHash->GetHash() != expectedHash) {
LOG(("CacheIndex::ParseRecords() - Hash mismatch, [is %x, should be %x]",
mRWHash->GetHash(), expectedHash));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
return;
}
@@ -2202,9 +2192,9 @@ CacheIndex::ParseRecords()
mJournalReadSuccessfully = false;
if (mJournalHandle) {
- StartReadingJournal();
+ StartReadingJournal(aProofOfLock);
} else {
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
}
return;
@@ -2221,7 +2211,7 @@ CacheIndex::ParseRecords()
if (NS_FAILED(rv)) {
LOG(("CacheIndex::ParseRecords() - CacheFileIOManager::Read() failed "
"synchronously [rv=0x%08x]", rv));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
return;
} else {
mRWPending = true;
@@ -2229,14 +2219,12 @@ CacheIndex::ParseRecords()
}
void
-CacheIndex::StartReadingJournal()
+CacheIndex::StartReadingJournal(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::StartReadingJournal()"));
nsresult rv;
- sLock.AssertCurrentThreadOwns();
-
MOZ_ASSERT(mJournalHandle);
MOZ_ASSERT(mIndexOnDiskIsValid);
MOZ_ASSERT(mTmpJournal.Count() == 0);
@@ -2248,7 +2236,7 @@ CacheIndex::StartReadingJournal()
if (entriesSize < 0 || entriesSize % sizeof(CacheIndexRecord)) {
LOG(("CacheIndex::StartReadingJournal() - Journal is corrupted"));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
return;
}
@@ -2262,21 +2250,19 @@ CacheIndex::StartReadingJournal()
if (NS_FAILED(rv)) {
LOG(("CacheIndex::StartReadingJournal() - CacheFileIOManager::Read() failed"
" synchronously [rv=0x%08x]", rv));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
} else {
mRWPending = true;
}
}
void
-CacheIndex::ParseJournal()
+CacheIndex::ParseJournal(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::ParseJournal()"));
nsresult rv;
- sLock.AssertCurrentThreadOwns();
-
MOZ_ASSERT(!mRWPending);
uint32_t entryCnt = (mJournalHandle->FileSize() -
@@ -2296,7 +2282,7 @@ CacheIndex::ParseJournal()
LOG(("CacheIndex::ParseJournal() - Invalid entry found in journal, "
"ignoring whole journal [dirty=%d, fresh=%d]", entry->IsDirty(),
entry->IsFresh()));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
return;
}
@@ -2321,12 +2307,12 @@ CacheIndex::ParseJournal()
if (mRWHash->GetHash() != expectedHash) {
LOG(("CacheIndex::ParseJournal() - Hash mismatch, [is %x, should be %x]",
mRWHash->GetHash(), expectedHash));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
return;
}
mJournalReadSuccessfully = true;
- FinishRead(true);
+ FinishRead(true, aProofOfLock);
return;
}
@@ -2341,7 +2327,7 @@ CacheIndex::ParseJournal()
if (NS_FAILED(rv)) {
LOG(("CacheIndex::ParseJournal() - CacheFileIOManager::Read() failed "
"synchronously [rv=0x%08x]", rv));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
return;
} else {
mRWPending = true;
@@ -2349,12 +2335,10 @@ CacheIndex::ParseJournal()
}
void
-CacheIndex::MergeJournal()
+CacheIndex::MergeJournal(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::MergeJournal()"));
- sLock.AssertCurrentThreadOwns();
-
for (auto iter = mTmpJournal.Iter(); !iter.Done(); iter.Next()) {
CacheIndexEntry* entry = iter.Get();
@@ -2363,7 +2347,7 @@ CacheIndex::MergeJournal()
CacheIndexEntry* entry2 = mIndex.GetEntry(*entry->Hash());
{
- CacheIndexEntryAutoManage emng(entry->Hash(), this);
+ CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
if (entry->IsRemoved()) {
if (entry2) {
entry2->MarkRemoved();
@@ -2414,10 +2398,9 @@ CacheIndex::EnsureCorrectStats()
}
void
-CacheIndex::FinishRead(bool aSucceeded)
+CacheIndex::FinishRead(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded));
- sLock.AssertCurrentThreadOwns();
MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == READING);
@@ -2471,27 +2454,27 @@ CacheIndex::FinishRead(bool aSucceeded)
if (!mIndexOnDiskIsValid) {
MOZ_ASSERT(mTmpJournal.Count() == 0);
EnsureNoFreshEntry();
- ProcessPendingOperations();
+ ProcessPendingOperations(aProofOfLock);
// Remove all entries that we haven't seen during this session
- RemoveNonFreshEntries();
- StartUpdatingIndex(true);
+ RemoveNonFreshEntries(aProofOfLock);
+ StartUpdatingIndex(true, aProofOfLock);
return;
}
if (!mJournalReadSuccessfully) {
mTmpJournal.Clear();
EnsureNoFreshEntry();
- ProcessPendingOperations();
- StartUpdatingIndex(false);
+ ProcessPendingOperations(aProofOfLock);
+ StartUpdatingIndex(false, aProofOfLock);
return;
}
- MergeJournal();
+ MergeJournal(aProofOfLock);
EnsureNoFreshEntry();
- ProcessPendingOperations();
+ ProcessPendingOperations(aProofOfLock);
mIndexStats.Log();
- ChangeState(READY);
+ ChangeState(READY, aProofOfLock);
mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
}
@@ -2508,17 +2491,15 @@ CacheIndex::DelayedUpdate(nsITimer *aTimer, void *aClosure)
return;
}
- index->DelayedUpdateLocked();
+ index->DelayedUpdateLocked(lock);
}
// static
void
-CacheIndex::DelayedUpdateLocked()
+CacheIndex::DelayedUpdateLocked(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::DelayedUpdateLocked()"));
- sLock.AssertCurrentThreadOwns();
-
nsresult rv;
mUpdateTimer = nullptr;
@@ -2549,7 +2530,7 @@ CacheIndex::DelayedUpdateLocked()
mUpdateEventPending = false;
NS_WARNING("CacheIndex::DelayedUpdateLocked() - Can't dispatch event");
LOG(("CacheIndex::DelayedUpdate() - Can't dispatch event" ));
- FinishUpdate(false);
+ FinishUpdate(false, aProofOfLock);
}
}
@@ -2655,12 +2636,10 @@ CacheIndex::IsUpdatePending()
}
void
-CacheIndex::BuildIndex()
+CacheIndex::BuildIndex(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::BuildIndex()"));
- sLock.AssertCurrentThreadOwns();
-
MOZ_ASSERT(mPendingUpdates.Count() == 0);
nsresult rv;
@@ -2678,7 +2657,7 @@ CacheIndex::BuildIndex()
}
if (NS_FAILED(rv)) {
- FinishUpdate(false);
+ FinishUpdate(false, aProofOfLock);
return;
}
}
@@ -2700,7 +2679,7 @@ CacheIndex::BuildIndex()
return;
}
if (!file) {
- FinishUpdate(NS_SUCCEEDED(rv));
+ FinishUpdate(NS_SUCCEEDED(rv), aProofOfLock);
return;
}
@@ -2780,7 +2759,7 @@ CacheIndex::BuildIndex()
"failed, removing file. [name=%s]", leaf.get()));
file->Remove(false);
} else {
- CacheIndexEntryAutoManage entryMng(&hash, this);
+ CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
entry = mIndex.PutEntry(hash);
InitEntryFromDiskData(entry, meta, size);
LOG(("CacheIndex::BuildIndex() - Added entry to index. [hash=%s]",
@@ -2793,7 +2772,8 @@ CacheIndex::BuildIndex()
}
bool
-CacheIndex::StartUpdatingIndexIfNeeded(bool aSwitchingToReadyState)
+CacheIndex::StartUpdatingIndexIfNeeded(const StaticMutexAutoLock& aProofOfLock,
+ bool aSwitchingToReadyState)
{
// Start updating process when we are in or we are switching to READY state
// and index needs update, but not during shutdown or when removing all
@@ -2802,7 +2782,7 @@ CacheIndex::StartUpdatingIndexIfNeeded(bool aSwitchingToReadyState)
!mShuttingDown && !mRemovingAll) {
LOG(("CacheIndex::StartUpdatingIndexIfNeeded() - starting update process"));
mIndexNeedsUpdate = false;
- StartUpdatingIndex(false);
+ StartUpdatingIndex(false, aProofOfLock);
return true;
}
@@ -2810,21 +2790,20 @@ CacheIndex::StartUpdatingIndexIfNeeded(bool aSwitchingToReadyState)
}
void
-CacheIndex::StartUpdatingIndex(bool aRebuild)
+CacheIndex::StartUpdatingIndex(bool aRebuild,
+ const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::StartUpdatingIndex() [rebuild=%d]", aRebuild));
- sLock.AssertCurrentThreadOwns();
-
nsresult rv;
mIndexStats.Log();
- ChangeState(aRebuild ? BUILDING : UPDATING);
+ ChangeState(aRebuild ? BUILDING : UPDATING, aProofOfLock);
mDontMarkIndexClean = false;
if (mShuttingDown || mRemovingAll) {
- FinishUpdate(false);
+ FinishUpdate(false, aProofOfLock);
return;
}
@@ -2861,17 +2840,15 @@ CacheIndex::StartUpdatingIndex(bool aRebuild)
mUpdateEventPending = false;
NS_WARNING("CacheIndex::StartUpdatingIndex() - Can't dispatch event");
LOG(("CacheIndex::StartUpdatingIndex() - Can't dispatch event" ));
- FinishUpdate(false);
+ FinishUpdate(false, aProofOfLock);
}
}
void
-CacheIndex::UpdateIndex()
+CacheIndex::UpdateIndex(const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::UpdateIndex()"));
- sLock.AssertCurrentThreadOwns();
-
MOZ_ASSERT(mPendingUpdates.Count() == 0);
nsresult rv;
@@ -2889,7 +2866,7 @@ CacheIndex::UpdateIndex()
}
if (NS_FAILED(rv)) {
- FinishUpdate(false);
+ FinishUpdate(false, aProofOfLock);
return;
}
}
@@ -2912,7 +2889,7 @@ CacheIndex::UpdateIndex()
return;
}
if (!file) {
- FinishUpdate(NS_SUCCEEDED(rv));
+ FinishUpdate(NS_SUCCEEDED(rv), aProofOfLock);
return;
}
@@ -2983,7 +2960,7 @@ CacheIndex::UpdateIndex()
"lastModifiedTime=%u]", leaf.get(), mIndexTimeStamp,
lastModifiedTime / PR_MSEC_PER_SEC));
- CacheIndexEntryAutoManage entryMng(&hash, this);
+ CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
entry->MarkFresh();
continue;
}
@@ -3015,7 +2992,7 @@ CacheIndex::UpdateIndex()
entry = mIndex.GetEntry(hash);
MOZ_ASSERT(!entry || !entry->IsFresh());
- CacheIndexEntryAutoManage entryMng(&hash, this);
+ CacheIndexEntryAutoManage entryMng(&hash, this, aProofOfLock);
if (NS_FAILED(rv)) {
LOG(("CacheIndex::UpdateIndex() - CacheFileMetadata::SyncReadMetadata() "
@@ -3039,15 +3016,13 @@ CacheIndex::UpdateIndex()
}
void
-CacheIndex::FinishUpdate(bool aSucceeded)
+CacheIndex::FinishUpdate(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded));
MOZ_ASSERT(mState == UPDATING || mState == BUILDING ||
(!aSucceeded && mState == SHUTDOWN));
- sLock.AssertCurrentThreadOwns();
-
if (mDirEnumerator) {
if (NS_IsMainThread()) {
LOG(("CacheIndex::FinishUpdate() - posting of PreShutdownInternal failed?"
@@ -3074,19 +3049,19 @@ CacheIndex::FinishUpdate(bool aSucceeded)
// If we've iterated over all entries successfully then all entries that
// really exist on the disk are now marked as fresh. All non-fresh entries
// don't exist anymore and must be removed from the index.
- RemoveNonFreshEntries();
+ RemoveNonFreshEntries(aProofOfLock);
}
// Make sure we won't start update. If the build or update failed, there is no
// reason to believe that it will succeed next time.
mIndexNeedsUpdate = false;
- ChangeState(READY);
+ ChangeState(READY, aProofOfLock);
mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
}
void
-CacheIndex::RemoveNonFreshEntries()
+CacheIndex::RemoveNonFreshEntries(const StaticMutexAutoLock& aProofOfLock)
{
for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
CacheIndexEntry* entry = iter.Get();
@@ -3098,7 +3073,7 @@ CacheIndex::RemoveNonFreshEntries()
"[hash=%08x%08x%08x%08x%08x]", LOGSHA1(entry->Hash())));
{
- CacheIndexEntryAutoManage emng(entry->Hash(), this);
+ CacheIndexEntryAutoManage emng(entry->Hash(), this, aProofOfLock);
emng.DoNotSearchInIndex();
}
@@ -3125,7 +3100,7 @@ CacheIndex::StateString(EState aState)
}
void
-CacheIndex::ChangeState(EState aNewState)
+CacheIndex::ChangeState(EState aNewState, const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::ChangeState() changing state %s -> %s", StateString(mState),
StateString(aNewState)));
@@ -3139,7 +3114,7 @@ CacheIndex::ChangeState(EState aNewState)
MOZ_ASSERT(!mShuttingDown || mState != READY || aNewState == SHUTDOWN);
// Start updating process when switching to READY state if needed
- if (aNewState == READY && StartUpdatingIndexIfNeeded(true)) {
+ if (aNewState == READY && StartUpdatingIndexIfNeeded(aProofOfLock, true)) {
return;
}
@@ -3213,23 +3188,25 @@ CacheIndex::ReleaseBuffer()
}
void
-CacheIndex::FrecencyArray::AppendRecord(CacheIndexRecord *aRecord)
+CacheIndex::FrecencyArray::AppendRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::FrecencyArray::AppendRecord() [record=%p, hash=%08x%08x%08x"
- "%08x%08x]", aRecord, LOGSHA1(aRecord->mHash)));
+ "%08x%08x]", aRecord, LOGSHA1(aRecord->Get()->mHash)));
MOZ_ASSERT(!mRecs.Contains(aRecord));
mRecs.AppendElement(aRecord);
// If the new frecency is 0, the element should be at the end of the array,
// i.e. this change doesn't affect order of the array
- if (aRecord->mFrecency != 0) {
+ if (aRecord->Get()->mFrecency != 0) {
++mUnsortedElements;
}
}
void
-CacheIndex::FrecencyArray::RemoveRecord(CacheIndexRecord *aRecord)
+CacheIndex::FrecencyArray::RemoveRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::FrecencyArray::RemoveRecord() [record=%p]", aRecord));
@@ -3241,12 +3218,13 @@ CacheIndex::FrecencyArray::RemoveRecord(CacheIndexRecord *aRecord)
// Calling SortIfNeeded ensures that we get rid of removed elements in the
// array once we hit the limit.
- SortIfNeeded();
+ SortIfNeeded(aProofOfLock);
}
void
-CacheIndex::FrecencyArray::ReplaceRecord(CacheIndexRecord *aOldRecord,
- CacheIndexRecord *aNewRecord)
+CacheIndex::FrecencyArray::ReplaceRecord(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::FrecencyArray::ReplaceRecord() [oldRecord=%p, "
"newRecord=%p]", aOldRecord, aNewRecord));
@@ -3258,7 +3236,7 @@ CacheIndex::FrecencyArray::ReplaceRecord(CacheIndexRecord *aOldRecord,
}
void
-CacheIndex::FrecencyArray::SortIfNeeded()
+CacheIndex::FrecencyArray::SortIfNeeded(const StaticMutexAutoLock& aProofOfLock)
{
const uint32_t kMaxUnsortedCount = 512;
const uint32_t kMaxUnsortedPercent = 10;
@@ -3290,44 +3268,41 @@ CacheIndex::FrecencyArray::SortIfNeeded()
}
void
-CacheIndex::AddRecordToIterators(CacheIndexRecord *aRecord)
+CacheIndex::AddRecordToIterators(CacheIndexRecordWrapper *aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
{
- sLock.AssertCurrentThreadOwns();
-
for (uint32_t i = 0; i < mIterators.Length(); ++i) {
// Add a new record only when iterator is supposed to be updated.
if (mIterators[i]->ShouldBeNewAdded()) {
- mIterators[i]->AddRecord(aRecord);
+ mIterators[i]->AddRecord(aRecord, aProofOfLock);
}
}
}
void
-CacheIndex::RemoveRecordFromIterators(CacheIndexRecord *aRecord)
+CacheIndex::RemoveRecordFromIterators(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
{
- sLock.AssertCurrentThreadOwns();
-
for (uint32_t i = 0; i < mIterators.Length(); ++i) {
// Remove the record from iterator always, it makes no sence to return
// non-existing entries. Also the pointer to the record is no longer valid
// once the entry is removed from index.
- mIterators[i]->RemoveRecord(aRecord);
+ mIterators[i]->RemoveRecord(aRecord, aProofOfLock);
}
}
void
-CacheIndex::ReplaceRecordInIterators(CacheIndexRecord *aOldRecord,
- CacheIndexRecord *aNewRecord)
+CacheIndex::ReplaceRecordInIterators(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock)
{
- sLock.AssertCurrentThreadOwns();
-
for (uint32_t i = 0; i < mIterators.Length(); ++i) {
// We have to replace the record always since the pointer is no longer
// valid after this point. NOTE: Replacing the record doesn't mean that
// a new entry was added, it just means that the data in the entry was
// changed (e.g. a file size) and we had to track this change in
// mPendingUpdates since mIndex was read-only.
- mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord);
+ mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord, aProofOfLock);
}
}
@@ -3350,10 +3325,10 @@ CacheIndex::Run()
switch (mState) {
case BUILDING:
- BuildIndex();
+ BuildIndex(lock);
break;
case UPDATING:
- UpdateIndex();
+ UpdateIndex(lock);
break;
default:
LOG(("CacheIndex::Run() - Update/Build was canceled"));
@@ -3363,8 +3338,10 @@ CacheIndex::Run()
}
nsresult
-CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener,
- CacheFileHandle *aHandle, nsresult aResult)
+CacheIndex::OnFileOpenedInternal(FileOpenHelper* aOpener,
+ CacheFileHandle* aHandle,
+ nsresult aResult,
+ const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, "
"result=0x%08x]", aOpener, aHandle, aResult));
@@ -3373,8 +3350,6 @@ CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener,
nsresult rv;
- sLock.AssertCurrentThreadOwns();
-
MOZ_RELEASE_ASSERT(IsIndexUsable());
if (mState == READY && mShuttingDown) {
@@ -3389,10 +3364,10 @@ CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener,
if (NS_FAILED(aResult)) {
LOG(("CacheIndex::OnFileOpenedInternal() - Can't open index file for "
"writing [rv=0x%08x]", aResult));
- FinishWrite(false);
+ FinishWrite(false, aProofOfLock);
} else {
mIndexHandle = aHandle;
- WriteRecords();
+ WriteRecords(aProofOfLock);
}
break;
case READING:
@@ -3401,14 +3376,14 @@ CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener,
if (NS_SUCCEEDED(aResult)) {
if (aHandle->FileSize() == 0) {
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
CacheFileIOManager::DoomFile(aHandle, nullptr);
break;
} else {
mIndexHandle = aHandle;
}
} else {
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
break;
}
} else if (aOpener == mJournalFileOpener) {
@@ -3437,7 +3412,7 @@ CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener,
LOG(("CacheIndex::OnFileOpenedInternal() - Unexpected state, all "
"files [%s, %s, %s] should never exist. Removing whole index.",
INDEX_NAME, JOURNAL_NAME, TEMP_INDEX_NAME));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
break;
}
}
@@ -3450,11 +3425,11 @@ CacheIndex::OnFileOpenedInternal(FileOpenHelper *aOpener,
if (NS_FAILED(rv)) {
LOG(("CacheIndex::OnFileOpenedInternal() - CacheFileIOManager::"
"RenameFile() failed synchronously [rv=0x%08x]", rv));
- FinishRead(false);
+ FinishRead(false, aProofOfLock);
break;
}
} else {
- StartReadingIndex();
+ StartReadingIndex(aProofOfLock);
}
break;
@@ -3498,7 +3473,7 @@ CacheIndex::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
MOZ_ASSERT(mIndexHandle == aHandle);
if (NS_FAILED(aResult)) {
- FinishWrite(false);
+ FinishWrite(false, lock);
} else {
if (mSkipEntries == mProcessEntries) {
rv = CacheFileIOManager::RenameFile(mIndexHandle,
@@ -3507,10 +3482,10 @@ CacheIndex::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
if (NS_FAILED(rv)) {
LOG(("CacheIndex::OnDataWritten() - CacheFileIOManager::"
"RenameFile() failed synchronously [rv=0x%08x]", rv));
- FinishWrite(false);
+ FinishWrite(false, lock);
}
} else {
- WriteRecords();
+ WriteRecords(lock);
}
}
break;
@@ -3543,12 +3518,12 @@ CacheIndex::OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult)
MOZ_ASSERT(mIndexHandle == aHandle || mJournalHandle == aHandle);
if (NS_FAILED(aResult)) {
- FinishRead(false);
+ FinishRead(false, lock);
} else {
if (!mIndexOnDiskIsValid) {
- ParseRecords();
+ ParseRecords(lock);
} else {
- ParseJournal();
+ ParseJournal(lock);
}
}
break;
@@ -3604,7 +3579,7 @@ CacheIndex::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult)
break;
}
- FinishWrite(NS_SUCCEEDED(aResult));
+ FinishWrite(NS_SUCCEEDED(aResult), lock);
break;
case READING:
// This is a result of renaming journal file to tmpfile. It is renamed
@@ -3618,9 +3593,9 @@ CacheIndex::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult)
}
if (NS_FAILED(aResult)) {
- FinishRead(false);
+ FinishRead(false, lock);
} else {
- StartReadingIndex();
+ StartReadingIndex(lock);
}
break;
default:
diff --git a/netwerk/cache2/CacheIndex.h b/netwerk/cache2/CacheIndex.h
index acb3bdd250..7d875da626 100644
--- a/netwerk/cache2/CacheIndex.h
+++ b/netwerk/cache2/CacheIndex.h
@@ -93,6 +93,19 @@ static_assert(
sizeof(CacheIndexRecord::mFlags) == sizeof(CacheIndexRecord),
"Unexpected sizeof(CacheIndexRecord)!");
+class CacheIndexRecordWrapper final
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheIndexRecordWrapper)
+
+ CacheIndexRecordWrapper() : mRec(MakeUnique<CacheIndexRecord>()) {}
+ CacheIndexRecord* Get() { return mRec.get(); }
+
+private:
+ ~CacheIndexRecordWrapper() = default;
+ UniquePtr<CacheIndexRecord> mRec;
+};
+
class CacheIndexEntry : public PLDHashEntryHdr
{
public:
@@ -102,9 +115,9 @@ public:
explicit CacheIndexEntry(KeyTypePointer aKey)
{
MOZ_COUNT_CTOR(CacheIndexEntry);
- mRec = new CacheIndexRecord();
- LOG(("CacheIndexEntry::CacheIndexEntry() - Created record [rec=%p]", mRec.get()));
- memcpy(&mRec->mHash, aKey, sizeof(SHA1Sum::Hash));
+ mRec = new CacheIndexRecordWrapper();
+ LOG(("CacheIndexEntry::CacheIndexEntry() - Created record [rec=%p]", mRec->Get()));
+ memcpy(&mRec->Get()->mHash, aKey, sizeof(SHA1Sum::Hash));
}
CacheIndexEntry(const CacheIndexEntry& aOther)
{
@@ -114,13 +127,13 @@ public:
{
MOZ_COUNT_DTOR(CacheIndexEntry);
LOG(("CacheIndexEntry::~CacheIndexEntry() - Deleting record [rec=%p]",
- mRec.get()));
+ mRec->Get()));
}
// KeyEquals(): does this entry match this key?
bool KeyEquals(KeyTypePointer aKey) const
{
- return memcmp(&mRec->mHash, aKey, sizeof(SHA1Sum::Hash)) == 0;
+ return memcmp(&mRec->Get()->mHash, aKey, sizeof(SHA1Sum::Hash)) == 0;
}
// KeyToPointer(): Convert KeyType to KeyTypePointer
@@ -138,74 +151,74 @@ public:
bool operator==(const CacheIndexEntry& aOther) const
{
- return KeyEquals(&aOther.mRec->mHash);
+ return KeyEquals(&aOther.mRec->Get()->mHash);
}
CacheIndexEntry& operator=(const CacheIndexEntry& aOther)
{
- MOZ_ASSERT(memcmp(&mRec->mHash, &aOther.mRec->mHash,
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, &aOther.mRec->Get()->mHash,
sizeof(SHA1Sum::Hash)) == 0);
- mRec->mFrecency = aOther.mRec->mFrecency;
- mRec->mExpirationTime = aOther.mRec->mExpirationTime;
- mRec->mOriginAttrsHash = aOther.mRec->mOriginAttrsHash;
- mRec->mFlags = aOther.mRec->mFlags;
+ mRec->Get()->mFrecency = aOther.mRec->Get()->mFrecency;
+ mRec->Get()->mExpirationTime = aOther.mRec->Get()->mExpirationTime;
+ mRec->Get()->mOriginAttrsHash = aOther.mRec->Get()->mOriginAttrsHash;
+ mRec->Get()->mFlags = aOther.mRec->Get()->mFlags;
return *this;
}
void InitNew()
{
- mRec->mFrecency = 0;
- mRec->mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
- mRec->mOriginAttrsHash = 0;
- mRec->mFlags = 0;
+ mRec->Get()->mFrecency = 0;
+ mRec->Get()->mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
+ mRec->Get()->mOriginAttrsHash = 0;
+ mRec->Get()->mFlags = 0;
}
void Init(OriginAttrsHash aOriginAttrsHash, bool aAnonymous, bool aPinned)
{
- MOZ_ASSERT(mRec->mFrecency == 0);
- MOZ_ASSERT(mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME);
- MOZ_ASSERT(mRec->mOriginAttrsHash == 0);
+ MOZ_ASSERT(mRec->Get()->mFrecency == 0);
+ MOZ_ASSERT(mRec->Get()->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME);
+ MOZ_ASSERT(mRec->Get()->mOriginAttrsHash == 0);
// When we init the entry it must be fresh and may be dirty
- MOZ_ASSERT((mRec->mFlags & ~kDirtyMask) == kFreshMask);
+ MOZ_ASSERT((mRec->Get()->mFlags & ~kDirtyMask) == kFreshMask);
- mRec->mOriginAttrsHash = aOriginAttrsHash;
- mRec->mFlags |= kInitializedMask;
+ mRec->Get()->mOriginAttrsHash = aOriginAttrsHash;
+ mRec->Get()->mFlags |= kInitializedMask;
if (aAnonymous) {
- mRec->mFlags |= kAnonymousMask;
+ mRec->Get()->mFlags |= kAnonymousMask;
}
if (aPinned) {
- mRec->mFlags |= kPinnedMask;
+ mRec->Get()->mFlags |= kPinnedMask;
}
}
- const SHA1Sum::Hash * Hash() const { return &mRec->mHash; }
+ const SHA1Sum::Hash * Hash() const { return &mRec->Get()->mHash; }
- bool IsInitialized() const { return !!(mRec->mFlags & kInitializedMask); }
+ bool IsInitialized() const { return !!(mRec->Get()->mFlags & kInitializedMask); }
- mozilla::net::OriginAttrsHash OriginAttrsHash() const { return mRec->mOriginAttrsHash; }
+ mozilla::net::OriginAttrsHash OriginAttrsHash() const { return mRec->Get()->mOriginAttrsHash; }
- bool Anonymous() const { return !!(mRec->mFlags & kAnonymousMask); }
+ bool Anonymous() const { return !!(mRec->Get()->mFlags & kAnonymousMask); }
- bool IsRemoved() const { return !!(mRec->mFlags & kRemovedMask); }
- void MarkRemoved() { mRec->mFlags |= kRemovedMask; }
+ bool IsRemoved() const { return !!(mRec->Get()->mFlags & kRemovedMask); }
+ void MarkRemoved() { mRec->Get()->mFlags |= kRemovedMask; }
- bool IsDirty() const { return !!(mRec->mFlags & kDirtyMask); }
- void MarkDirty() { mRec->mFlags |= kDirtyMask; }
- void ClearDirty() { mRec->mFlags &= ~kDirtyMask; }
+ bool IsDirty() const { return !!(mRec->Get()->mFlags & kDirtyMask); }
+ void MarkDirty() { mRec->Get()->mFlags |= kDirtyMask; }
+ void ClearDirty() { mRec->Get()->mFlags &= ~kDirtyMask; }
- bool IsFresh() const { return !!(mRec->mFlags & kFreshMask); }
- void MarkFresh() { mRec->mFlags |= kFreshMask; }
+ bool IsFresh() const { return !!(mRec->Get()->mFlags & kFreshMask); }
+ void MarkFresh() { mRec->Get()->mFlags |= kFreshMask; }
- bool IsPinned() const { return !!(mRec->mFlags & kPinnedMask); }
+ bool IsPinned() const { return !!(mRec->Get()->mFlags & kPinnedMask); }
- void SetFrecency(uint32_t aFrecency) { mRec->mFrecency = aFrecency; }
- uint32_t GetFrecency() const { return mRec->mFrecency; }
+ void SetFrecency(uint32_t aFrecency) { mRec->Get()->mFrecency = aFrecency; }
+ uint32_t GetFrecency() const { return mRec->Get()->mFrecency; }
void SetExpirationTime(uint32_t aExpirationTime)
{
- mRec->mExpirationTime = aExpirationTime;
+ mRec->Get()->mExpirationTime = aExpirationTime;
}
- uint32_t GetExpirationTime() const { return mRec->mExpirationTime; }
+ uint32_t GetExpirationTime() const { return mRec->Get()->mExpirationTime; }
// Sets filesize in kilobytes.
void SetFileSize(uint32_t aFileSize)
@@ -215,11 +228,11 @@ public:
"truncating to %u", kFileSizeMask));
aFileSize = kFileSizeMask;
}
- mRec->mFlags &= ~kFileSizeMask;
- mRec->mFlags |= aFileSize;
+ mRec->Get()->mFlags &= ~kFileSizeMask;
+ mRec->Get()->mFlags |= aFileSize;
}
// Returns filesize in kilobytes.
- uint32_t GetFileSize() const { return GetFileSize(mRec); }
+ uint32_t GetFileSize() const { return GetFileSize(mRec->Get()); }
static uint32_t GetFileSize(CacheIndexRecord *aRec)
{
return aRec->mFlags & kFileSizeMask;
@@ -233,40 +246,48 @@ public:
void WriteToBuf(void *aBuf)
{
uint8_t* ptr = static_cast<uint8_t*>(aBuf);
- memcpy(ptr, mRec->mHash, sizeof(SHA1Sum::Hash)); ptr += sizeof(SHA1Sum::Hash);
- NetworkEndian::writeUint32(ptr, mRec->mFrecency); ptr += sizeof(uint32_t);
- NetworkEndian::writeUint64(ptr, mRec->mOriginAttrsHash); ptr += sizeof(uint64_t);
- NetworkEndian::writeUint32(ptr, mRec->mExpirationTime); ptr += sizeof(uint32_t);
+ memcpy(ptr, mRec->Get()->mHash, sizeof(SHA1Sum::Hash));
+ ptr += sizeof(SHA1Sum::Hash);
+ NetworkEndian::writeUint32(ptr, mRec->Get()->mFrecency);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint64(ptr, mRec->Get()->mOriginAttrsHash);
+ ptr += sizeof(uint64_t);
+ NetworkEndian::writeUint32(ptr, mRec->Get()->mExpirationTime);
+ ptr += sizeof(uint32_t);
// Dirty and fresh flags should never go to disk, since they make sense only
// during current session.
- NetworkEndian::writeUint32(ptr, mRec->mFlags & ~(kDirtyMask | kFreshMask));
+ NetworkEndian::writeUint32(ptr, mRec->Get()->mFlags & ~(kDirtyMask | kFreshMask));
}
void ReadFromBuf(void *aBuf)
{
const uint8_t* ptr = static_cast<const uint8_t*>(aBuf);
- MOZ_ASSERT(memcmp(&mRec->mHash, ptr, sizeof(SHA1Sum::Hash)) == 0); ptr += sizeof(SHA1Sum::Hash);
- mRec->mFrecency = NetworkEndian::readUint32(ptr); ptr += sizeof(uint32_t);
- mRec->mOriginAttrsHash = NetworkEndian::readUint64(ptr); ptr += sizeof(uint64_t);
- mRec->mExpirationTime = NetworkEndian::readUint32(ptr); ptr += sizeof(uint32_t);
- mRec->mFlags = NetworkEndian::readUint32(ptr);
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, ptr, sizeof(SHA1Sum::Hash)) == 0);
+ ptr += sizeof(SHA1Sum::Hash);
+ mRec->Get()->mFrecency = NetworkEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mRec->Get()->mOriginAttrsHash = NetworkEndian::readUint64(ptr);
+ ptr += sizeof(uint64_t);
+ mRec->Get()->mExpirationTime = NetworkEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mRec->Get()->mFlags = NetworkEndian::readUint32(ptr);
}
void Log() const {
LOG(("CacheIndexEntry::Log() [this=%p, hash=%08x%08x%08x%08x%08x, fresh=%u,"
" initialized=%u, removed=%u, dirty=%u, anonymous=%u, "
"originAttrsHash=%llx, frecency=%u, expirationTime=%u, size=%u]",
- this, LOGSHA1(mRec->mHash), IsFresh(), IsInitialized(), IsRemoved(),
- IsDirty(), Anonymous(), OriginAttrsHash(), GetFrecency(),
+ this, LOGSHA1(mRec->Get()->mHash), IsFresh(), IsInitialized(),
+ IsRemoved(), IsDirty(), Anonymous(), OriginAttrsHash(), GetFrecency(),
GetExpirationTime(), GetFileSize()));
}
- static bool RecordMatchesLoadContextInfo(CacheIndexRecord *aRec,
+ static bool RecordMatchesLoadContextInfo(CacheIndexRecordWrapper *aRec,
nsILoadContextInfo *aInfo)
{
if (!aInfo->IsPrivate() &&
- GetOriginAttrsHash(*aInfo->OriginAttributesPtr()) == aRec->mOriginAttrsHash &&
- aInfo->IsAnonymous() == !!(aRec->mFlags & kAnonymousMask)) {
+ GetOriginAttrsHash(*aInfo->OriginAttributesPtr()) == aRec->Get()->mOriginAttrsHash &&
+ aInfo->IsAnonymous() == !!(aRec->Get()->mFlags & kAnonymousMask)) {
return true;
}
@@ -276,7 +297,7 @@ public:
// Memory reporting
size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
{
- return mallocSizeOf(mRec.get());
+ return mallocSizeOf(mRec->Get());
}
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
@@ -313,7 +334,7 @@ private:
// FileSize in kilobytes
static const uint32_t kFileSizeMask = 0x00FFFFFF;
- nsAutoPtr<CacheIndexRecord> mRec;
+ RefPtr<CacheIndexRecordWrapper> mRec;
};
class CacheIndexEntryUpdate : public CacheIndexEntry
@@ -334,7 +355,7 @@ public:
CacheIndexEntryUpdate& operator=(const CacheIndexEntry& aOther)
{
- MOZ_ASSERT(memcmp(&mRec->mHash, &aOther.mRec->mHash,
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, &aOther.mRec->Get()->mHash,
sizeof(SHA1Sum::Hash)) == 0);
mUpdateFlags = 0;
*(static_cast<CacheIndexEntry *>(this)) = aOther;
@@ -367,21 +388,21 @@ public:
}
void ApplyUpdate(CacheIndexEntry *aDst) {
- MOZ_ASSERT(memcmp(&mRec->mHash, &aDst->mRec->mHash,
+ MOZ_ASSERT(memcmp(&mRec->Get()->mHash, &aDst->mRec->Get()->mHash,
sizeof(SHA1Sum::Hash)) == 0);
if (mUpdateFlags & kFrecencyUpdatedMask) {
- aDst->mRec->mFrecency = mRec->mFrecency;
+ aDst->mRec->Get()->mFrecency = mRec->Get()->mFrecency;
}
if (mUpdateFlags & kExpirationUpdatedMask) {
- aDst->mRec->mExpirationTime = mRec->mExpirationTime;
+ aDst->mRec->Get()->mExpirationTime = mRec->Get()->mExpirationTime;
}
- aDst->mRec->mOriginAttrsHash = mRec->mOriginAttrsHash;
+ aDst->mRec->Get()->mOriginAttrsHash = mRec->Get()->mOriginAttrsHash;
if (mUpdateFlags & kFileSizeUpdatedMask) {
- aDst->mRec->mFlags = mRec->mFlags;
+ aDst->mRec->Get()->mFlags = mRec->Get()->mFlags;
} else {
// Copy all flags except file size.
- aDst->mRec->mFlags &= kFileSizeMask;
- aDst->mRec->mFlags |= (mRec->mFlags & ~kFileSizeMask);
+ aDst->mRec->Get()->mFlags &= kFileSizeMask;
+ aDst->mRec->Get()->mFlags |= (mRec->Get()->mFlags & ~kFileSizeMask);
}
}
@@ -698,7 +719,9 @@ private:
NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) override;
nsresult OnFileOpenedInternal(FileOpenHelper *aOpener,
- CacheFileHandle *aHandle, nsresult aResult);
+ CacheFileHandle *aHandle,
+ nsresult aResult,
+ const StaticMutexAutoLock& aProofOfLock);
NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
nsresult aResult) override;
NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) override;
@@ -706,7 +729,7 @@ private:
NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) override;
NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) override;
- nsresult InitInternal(nsIFile *aCacheDirectory);
+ nsresult InitInternal(nsIFile *aCacheDirectory, const StaticMutexAutoLock& aProofOfLock);
void PreShutdownInternal();
// This method returns false when index is not initialized or is shut down.
@@ -727,7 +750,7 @@ private:
const uint32_t *aSize);
// Merge all pending operations from mPendingUpdates into mIndex.
- void ProcessPendingOperations();
+ void ProcessPendingOperations(const StaticMutexAutoLock& aProofOfLock);
// Following methods perform writing of the index file.
//
@@ -739,14 +762,14 @@ private:
//
// Starts writing of index when both limits (minimal delay between writes and
// minimum number of changes in index) were exceeded.
- bool WriteIndexToDiskIfNeeded();
+ bool WriteIndexToDiskIfNeeded(const StaticMutexAutoLock& aProofOfLock);
// Starts writing of index file.
- void WriteIndexToDisk();
+ void WriteIndexToDisk(const StaticMutexAutoLock& aProofOfLock);
// Serializes part of mIndex hashtable to the write buffer a writes the buffer
// to the file.
- void WriteRecords();
+ void WriteRecords(const StaticMutexAutoLock& aProofOfLock);
// Finalizes writing process.
- void FinishWrite(bool aSucceeded);
+ void FinishWrite(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock);
// Following methods perform writing of the journal during shutdown. All these
// methods must be called only during shutdown since they write/delete files
@@ -799,17 +822,17 @@ private:
// FF crashes during parsing of the index.
//
// Initiates reading index from disk.
- void ReadIndexFromDisk();
+ void ReadIndexFromDisk(const StaticMutexAutoLock& aProofOfLock);
// Starts reading data from index file.
- void StartReadingIndex();
+ void StartReadingIndex(const StaticMutexAutoLock& aProofOfLock);
// Parses data read from index file.
- void ParseRecords();
+ void ParseRecords(const StaticMutexAutoLock& aProofOfLock);
// Starts reading data from journal file.
- void StartReadingJournal();
+ void StartReadingJournal(const StaticMutexAutoLock& aProofOfLock);
// Parses data read from journal file.
- void ParseJournal();
+ void ParseJournal(const StaticMutexAutoLock& aProofOfLock);
// Merges entries from journal into mIndex.
- void MergeJournal();
+ void MergeJournal(const StaticMutexAutoLock& aProofOfLock);
// In debug build this method checks that we have no fresh entry in mIndex
// after we finish reading index and before we process pending operations.
void EnsureNoFreshEntry();
@@ -817,12 +840,12 @@ private:
// to make sure mIndexStats contains correct information.
void EnsureCorrectStats();
// Finalizes reading process.
- void FinishRead(bool aSucceeded);
+ void FinishRead(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock);
// Following methods perform updating and building of the index.
// Timer callback that starts update or build process.
static void DelayedUpdate(nsITimer *aTimer, void *aClosure);
- void DelayedUpdateLocked();
+ void DelayedUpdateLocked(const StaticMutexAutoLock& aProofOfLock);
// Posts timer event that start update or build process.
nsresult ScheduleUpdateTimer(uint32_t aDelay);
nsresult SetupDirectoryEnumerator();
@@ -833,20 +856,22 @@ private:
bool IsUpdatePending();
// Iterates through all files in entries directory that we didn't create/open
// during this session, parses them and adds the entries to the index.
- void BuildIndex();
+ void BuildIndex(const StaticMutexAutoLock& aProofOfLock);
- bool StartUpdatingIndexIfNeeded(bool aSwitchingToReadyState = false);
+ bool StartUpdatingIndexIfNeeded(const StaticMutexAutoLock& aProofOfLock,
+ bool aSwitchingToReadyState = false);
// Starts update or build process or fires a timer when it is too early after
// startup.
- void StartUpdatingIndex(bool aRebuild);
+ void StartUpdatingIndex(bool aRebuild,
+ const StaticMutexAutoLock& aProofOfLock);
// Iterates through all files in entries directory that we didn't create/open
// during this session and theirs last modified time is newer than timestamp
// in the index header. Parses the files and adds the entries to the index.
- void UpdateIndex();
+ void UpdateIndex(const StaticMutexAutoLock& aProofOfLock);
// Finalizes update or build process.
- void FinishUpdate(bool aSucceeded);
+ void FinishUpdate(bool aSucceeded, const StaticMutexAutoLock& aProofOfLock);
- void RemoveNonFreshEntries();
+ void RemoveNonFreshEntries(const StaticMutexAutoLock& aProofOfLock);
enum EState {
// Initial state in which the index is not usable
@@ -899,7 +924,7 @@ private:
};
static char const * StateString(EState aState);
- void ChangeState(EState aNewState);
+ void ChangeState(EState aNewState, const StaticMutexAutoLock& aProofOfLock);
void NotifyAsyncGetDiskConsumptionCallbacks();
// Allocates and releases buffer used for reading and writing index.
@@ -907,10 +932,13 @@ private:
void ReleaseBuffer();
// Methods used by CacheIndexEntryAutoManage to keep the iterators up to date.
- void AddRecordToIterators(CacheIndexRecord *aRecord);
- void RemoveRecordFromIterators(CacheIndexRecord *aRecord);
- void ReplaceRecordInIterators(CacheIndexRecord *aOldRecord,
- CacheIndexRecord *aNewRecord);
+ void AddRecordToIterators(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void RemoveRecordFromIterators(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void ReplaceRecordInIterators(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock);
// Memory reporting (private part)
size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
@@ -1023,7 +1051,7 @@ private:
class Iterator
{
public:
- explicit Iterator(nsTArray<CacheIndexRecord *> *aRecs)
+ explicit Iterator(nsTArray<RefPtr<CacheIndexRecordWrapper>>* aRecs)
: mRecs(aRecs)
, mIdx(0)
{
@@ -1034,7 +1062,7 @@ private:
bool Done() const { return mIdx == mRecs->Length(); }
- CacheIndexRecord* Get() const
+ CacheIndexRecordWrapper* Get() const
{
MOZ_ASSERT(!Done());
return (*mRecs)[mIdx];
@@ -1050,7 +1078,7 @@ private:
}
private:
- nsTArray<CacheIndexRecord *> *mRecs;
+ nsTArray<RefPtr<CacheIndexRecordWrapper>>* mRecs;
uint32_t mIdx;
};
@@ -1061,19 +1089,22 @@ private:
, mRemovedElements(0) {}
// Methods used by CacheIndexEntryAutoManage to keep the array up to date.
- void AppendRecord(CacheIndexRecord *aRecord);
- void RemoveRecord(CacheIndexRecord *aRecord);
- void ReplaceRecord(CacheIndexRecord *aOldRecord,
- CacheIndexRecord *aNewRecord);
- void SortIfNeeded();
+ void AppendRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void RemoveRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void ReplaceRecord(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void SortIfNeeded(const StaticMutexAutoLock& aProofOfLock);
size_t Length() const { return mRecs.Length() - mRemovedElements; }
- void Clear() { mRecs.Clear(); }
+ void Clear(const StaticMutexAutoLock& aProofOfLock) { mRecs.Clear(); }
private:
friend class CacheIndex;
- nsTArray<CacheIndexRecord *> mRecs;
+ nsTArray<RefPtr<CacheIndexRecordWrapper>> mRecs;
uint32_t mUnsortedElements;
// Instead of removing elements from the array immediately, we null them out
// and the iterator skips them when accessing the array. The null pointers
diff --git a/netwerk/cache2/CacheIndexContextIterator.cpp b/netwerk/cache2/CacheIndexContextIterator.cpp
index 5f3cb7bd7c..570df2e058 100644
--- a/netwerk/cache2/CacheIndexContextIterator.cpp
+++ b/netwerk/cache2/CacheIndexContextIterator.cpp
@@ -24,20 +24,11 @@ CacheIndexContextIterator::~CacheIndexContextIterator()
}
void
-CacheIndexContextIterator::AddRecord(CacheIndexRecord *aRecord)
+CacheIndexContextIterator::AddRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
{
if (CacheIndexEntry::RecordMatchesLoadContextInfo(aRecord, mInfo)) {
- CacheIndexIterator::AddRecord(aRecord);
- }
-}
-
-void
-CacheIndexContextIterator::AddRecords(
- const nsTArray<CacheIndexRecord *> &aRecords)
-{
- // We need to add one by one so that those with wrong context are ignored.
- for (uint32_t i = 0; i < aRecords.Length(); ++i) {
- AddRecord(aRecords[i]);
+ CacheIndexIterator::AddRecord(aRecord, aProofOfLock);
}
}
diff --git a/netwerk/cache2/CacheIndexContextIterator.h b/netwerk/cache2/CacheIndexContextIterator.h
index 32eb9c4789..a0a595ae68 100644
--- a/netwerk/cache2/CacheIndexContextIterator.h
+++ b/netwerk/cache2/CacheIndexContextIterator.h
@@ -20,8 +20,8 @@ public:
virtual ~CacheIndexContextIterator();
private:
- virtual void AddRecord(CacheIndexRecord *aRecord);
- virtual void AddRecords(const nsTArray<CacheIndexRecord *> &aRecords);
+ virtual void AddRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock) override;
nsCOMPtr<nsILoadContextInfo> mInfo;
};
diff --git a/netwerk/cache2/CacheIndexIterator.cpp b/netwerk/cache2/CacheIndexIterator.cpp
index 0d56ec81f5..6715b70835 100644
--- a/netwerk/cache2/CacheIndexIterator.cpp
+++ b/netwerk/cache2/CacheIndexIterator.cpp
@@ -24,7 +24,9 @@ CacheIndexIterator::~CacheIndexIterator()
{
LOG(("CacheIndexIterator::~CacheIndexIterator() [this=%p]", this));
- Close();
+ StaticMutexAutoLock lock(CacheIndex::sLock);
+ ClearRecords(lock);
+ CloseInternal(NS_ERROR_NOT_AVAILABLE);
}
nsresult
@@ -43,7 +45,7 @@ CacheIndexIterator::GetNextHash(SHA1Sum::Hash *aHash)
return mStatus;
}
- memcpy(aHash, mRecords[mRecords.Length() - 1]->mHash, sizeof(SHA1Sum::Hash));
+ memcpy(aHash, mRecords[mRecords.Length() - 1]->Get()->mHash, sizeof(SHA1Sum::Hash));
mRecords.RemoveElementAt(mRecords.Length() - 1);
return NS_OK;
@@ -82,8 +84,13 @@ CacheIndexIterator::CloseInternal(nsresult aStatus)
return NS_OK;
}
-void
-CacheIndexIterator::AddRecord(CacheIndexRecord *aRecord)
+void CacheIndexIterator::ClearRecords(const StaticMutexAutoLock& aProofOfLock)
+{
+ mRecords.Clear();
+}
+
+void CacheIndexIterator::AddRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndexIterator::AddRecord() [this=%p, record=%p]", this, aRecord));
@@ -91,7 +98,8 @@ CacheIndexIterator::AddRecord(CacheIndexRecord *aRecord)
}
bool
-CacheIndexIterator::RemoveRecord(CacheIndexRecord *aRecord)
+CacheIndexIterator::RemoveRecord(CacheIndexRecordWrapper *aRecord,
+ const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndexIterator::RemoveRecord() [this=%p, record=%p]", this,
aRecord));
@@ -100,14 +108,15 @@ CacheIndexIterator::RemoveRecord(CacheIndexRecord *aRecord)
}
bool
-CacheIndexIterator::ReplaceRecord(CacheIndexRecord *aOldRecord,
- CacheIndexRecord *aNewRecord)
+CacheIndexIterator::ReplaceRecord(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock)
{
LOG(("CacheIndexIterator::ReplaceRecord() [this=%p, oldRecord=%p, "
"newRecord=%p]", this, aOldRecord, aNewRecord));
- if (RemoveRecord(aOldRecord)) {
- AddRecord(aNewRecord);
+ if (RemoveRecord(aOldRecord, aProofOfLock)) {
+ AddRecord(aNewRecord, aProofOfLock);
return true;
}
diff --git a/netwerk/cache2/CacheIndexIterator.h b/netwerk/cache2/CacheIndexIterator.h
index 9fe96989ec..fd38fc2a03 100644
--- a/netwerk/cache2/CacheIndexIterator.h
+++ b/netwerk/cache2/CacheIndexIterator.h
@@ -9,12 +9,13 @@
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "mozilla/SHA1.h"
+#include "mozilla/StaticMutex.h"
namespace mozilla {
namespace net {
class CacheIndex;
-struct CacheIndexRecord;
+class CacheIndexRecordWrapper;
class CacheIndexIterator
{
@@ -42,14 +43,18 @@ protected:
nsresult CloseInternal(nsresult aStatus);
bool ShouldBeNewAdded() { return mAddNew; }
- virtual void AddRecord(CacheIndexRecord *aRecord);
- bool RemoveRecord(CacheIndexRecord *aRecord);
- bool ReplaceRecord(CacheIndexRecord *aOldRecord,
- CacheIndexRecord *aNewRecord);
+ virtual void AddRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ bool RemoveRecord(CacheIndexRecordWrapper* aRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ bool ReplaceRecord(CacheIndexRecordWrapper* aOldRecord,
+ CacheIndexRecordWrapper* aNewRecord,
+ const StaticMutexAutoLock& aProofOfLock);
+ void ClearRecords(const StaticMutexAutoLock& aProofOfLock);
nsresult mStatus;
RefPtr<CacheIndex> mIndex;
- nsTArray<CacheIndexRecord *> mRecords;
+ nsTArray<RefPtr<CacheIndexRecordWrapper>> mRecords;
bool mAddNew;
};
diff --git a/old-configure.in b/old-configure.in
index df3d4d81e9..449bf2ee14 100644
--- a/old-configure.in
+++ b/old-configure.in
@@ -68,7 +68,7 @@ GNOMEUI_VERSION=2.2.0
GCONF_VERSION=1.2.1
STARTUP_NOTIFICATION_VERSION=0.8
DBUS_VERSION=0.60
-SQLITE_VERSION=3.27.2
+SQLITE_VERSION=3.36.0
dnl Set various checks
dnl ========================================================
diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp
index d0acdb9ec2..9acc2d7b7e 100644
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -159,6 +159,7 @@
/* This must occur *after* base/process_util.h to avoid typedefs conflicts. */
#include "mozilla/LinkedList.h"
#include "mozilla/MemoryReporting.h"
+#include "mozilla/Move.h"
#include "mozilla/SegmentedVector.h"
#include "nsCycleCollectionParticipant.h"
@@ -961,14 +962,43 @@ CanonicalizeParticipant(void** aParti, nsCycleCollectionParticipant** aCp)
struct nsPurpleBufferEntry
{
- union
+ nsPurpleBufferEntry(void* aObject, nsCycleCollectingAutoRefCnt* aRefCnt,
+ nsCycleCollectionParticipant* aParticipant)
+ : mObject(aObject)
+ , mRefCnt(aRefCnt)
+ , mParticipant(aParticipant)
+ {}
+
+ nsPurpleBufferEntry(nsPurpleBufferEntry&& aOther)
+ : mObject(nullptr)
+ , mRefCnt(nullptr)
+ , mParticipant(nullptr)
{
- void* mObject; // when low bit unset
- nsPurpleBufferEntry* mNextInFreeList; // when low bit set
- };
+ Swap(aOther);
+ }
- nsCycleCollectingAutoRefCnt* mRefCnt;
+ void Swap(nsPurpleBufferEntry& aOther) {
+ std::swap(mObject, aOther.mObject);
+ std::swap(mRefCnt, aOther.mRefCnt);
+ std::swap(mParticipant, aOther.mParticipant);
+ }
+
+ void Clear() {
+ mRefCnt->RemoveFromPurpleBuffer();
+ mRefCnt = nullptr;
+ mObject = nullptr;
+ mParticipant = nullptr;
+ }
+
+ ~nsPurpleBufferEntry() {
+ if (mRefCnt) {
+ mRefCnt->RemoveFromPurpleBuffer();
+ }
+ }
+ void* mObject;
+
+ nsCycleCollectingAutoRefCnt* mRefCnt;
nsCycleCollectionParticipant* mParticipant; // nullptr for nsISupports
};
@@ -977,118 +1007,113 @@ class nsCycleCollector;
struct nsPurpleBuffer
{
private:
- struct PurpleBlock
- {
- PurpleBlock* mNext;
- // Try to match the size of a jemalloc bucket, to minimize slop bytes.
- // - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries
- // is 16,380 bytes, which leaves 4 bytes for mNext.
- // - On 64-bit platforms sizeof(nsPurpleBufferEntry) is 24, so mEntries
- // is 32,544 bytes, which leaves 8 bytes for mNext.
- nsPurpleBufferEntry mEntries[1365];
-
- PurpleBlock() : mNext(nullptr)
- {
- // Ensure PurpleBlock is the right size (see above).
- static_assert(
- sizeof(PurpleBlock) == 16384 || // 32-bit
- sizeof(PurpleBlock) == 32768, // 64-bit
- "ill-sized nsPurpleBuffer::PurpleBlock"
- );
-
- InitNextPointers();
- }
-
- // Put all the entries in the block on the free list.
- void InitNextPointers()
- {
- for (uint32_t i = 1; i < ArrayLength(mEntries); ++i) {
- mEntries[i - 1].mNextInFreeList =
- (nsPurpleBufferEntry*)(uintptr_t(mEntries + i) | 1);
- }
- mEntries[ArrayLength(mEntries) - 1].mNextInFreeList =
- (nsPurpleBufferEntry*)1;
- }
-
- template<class PurpleVisitor>
- void VisitEntries(nsPurpleBuffer& aBuffer, PurpleVisitor& aVisitor)
- {
- nsPurpleBufferEntry* eEnd = ArrayEnd(mEntries);
- for (nsPurpleBufferEntry* e = mEntries; e != eEnd; ++e) {
- MOZ_ASSERT(e->mObject, "There should be no null mObject when we iterate over the purple buffer");
- if (!(uintptr_t(e->mObject) & uintptr_t(1)) && e->mObject) {
- aVisitor.Visit(aBuffer, e);
- }
- }
- }
- };
- // This class wraps a linked list of the elements in the purple
- // buffer.
-
uint32_t mCount;
- PurpleBlock mFirstBlock;
- nsPurpleBufferEntry* mFreeList;
+ // Try to match the size of a jemalloc bucket, to minimize slop bytes.
+ // - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries'
+ // Segment is 16,372 bytes.
+ // - On 64-bit platforms sizeof(nsPurpleBufferEntry) is 24, so mEntries'
+ // Segment is 32,760 bytes.
+ static const uint32_t kEntriesPerSegment = 1365;
+ static const size_t kSegmentSize =
+ sizeof(nsPurpleBufferEntry) * kEntriesPerSegment;
+ typedef
+ SegmentedVector<nsPurpleBufferEntry, kSegmentSize, InfallibleAllocPolicy>
+ PurpleBufferVector;
+ PurpleBufferVector mEntries;
public:
nsPurpleBuffer()
+ : mCount(0)
{
- InitBlocks();
+ static_assert(
+ sizeof(PurpleBufferVector::Segment) == 16372 || // 32-bit
+ sizeof(PurpleBufferVector::Segment) == 32760 || // 64-bit
+ sizeof(PurpleBufferVector::Segment) == 32744, // 64-bit Windows
+ "ill-sized nsPurpleBuffer::mEntries");
}
~nsPurpleBuffer()
- {
- FreeBlocks();
- }
+ {}
+ // This method compacts mEntries.
template<class PurpleVisitor>
- void VisitEntries(PurpleVisitor& aVisitor)
- {
- for (PurpleBlock* b = &mFirstBlock; b; b = b->mNext) {
- b->VisitEntries(*this, aVisitor);
+ void VisitEntries(PurpleVisitor& aVisitor) {
+ if (mEntries.IsEmpty()) {
+ return;
}
- }
- void InitBlocks()
- {
- mCount = 0;
- mFreeList = mFirstBlock.mEntries;
- }
+ uint32_t oldLength = mEntries.Length();
+ uint32_t newLength = 0;
+ auto revIter = mEntries.IterFromLast();
+ auto iter = mEntries.Iter();
+ // After iteration this points to the first empty entry.
+ auto firstEmptyIter = mEntries.Iter();
+ auto iterFromLastEntry = mEntries.IterFromLast();
+ for (; !iter.Done(); iter.Next()) {
+ nsPurpleBufferEntry& e = iter.Get();
+ if (e.mObject) {
+ aVisitor.Visit(*this, &e);
+ }
- void FreeBlocks()
- {
- if (mCount > 0) {
- UnmarkRemainingPurple(&mFirstBlock);
- }
- PurpleBlock* b = mFirstBlock.mNext;
- while (b) {
- if (mCount > 0) {
- UnmarkRemainingPurple(b);
+ // Visit call above may have cleared the entry, or the entry was empty
+ // already.
+ if (!e.mObject) {
+ // Try to find a non-empty entry from the end of the vector.
+ for (; !revIter.Done(); revIter.Prev()) {
+ nsPurpleBufferEntry& otherEntry = revIter.Get();
+ if (&e == &otherEntry) {
+ break;
+ }
+ if (otherEntry.mObject) {
+ aVisitor.Visit(*this, &otherEntry);
+ // Visit may have cleared otherEntry.
+ if (otherEntry.mObject) {
+ e.Swap(otherEntry);
+ revIter.Prev(); // We've swapped this now empty entry.
+ break;
+ }
+ }
+ }
+ }
+
+ // Entry is non-empty even after the Visit call, ensure it is kept
+ // in mEntries.
+ if (e.mObject) {
+ firstEmptyIter.Next();
+ ++newLength;
+ }
+
+ if (&e == &revIter.Get()) {
+ break;
}
- PurpleBlock* next = b->mNext;
- delete b;
- b = next;
}
- mFirstBlock.mNext = nullptr;
- }
- struct UnmarkRemainingPurpleVisitor
- {
- void
- Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry)
- {
- if (aEntry->mRefCnt) {
- aEntry->mRefCnt->RemoveFromPurpleBuffer();
- aEntry->mRefCnt = nullptr;
+ // There were some empty entries.
+ if (oldLength != newLength) {
+
+ // While visiting entries, some new ones were possibly added. This can
+ // happen during CanSkip. Move all such new entries to be after other
+ // entries. Note, we don't call Visit on newly added entries!
+ if (&iterFromLastEntry.Get() != &mEntries.GetLast()) {
+ iterFromLastEntry.Next(); // Now pointing to the first added entry.
+ auto& iterForNewEntries = iterFromLastEntry;
+ while (!iterForNewEntries.Done()) {
+ MOZ_ASSERT(!firstEmptyIter.Done());
+ MOZ_ASSERT(!firstEmptyIter.Get().mObject);
+ firstEmptyIter.Get().Swap(iterForNewEntries.Get());
+ firstEmptyIter.Next();
+ iterForNewEntries.Next();
+ ++newLength; // We keep all the new entries.
+ }
}
- aEntry->mObject = nullptr;
- --aBuffer.mCount;
+
+ mEntries.PopLastN(oldLength - newLength);
}
- };
+ }
- void UnmarkRemainingPurple(PurpleBlock* aBlock)
- {
- UnmarkRemainingPurpleVisitor visitor;
- aBlock->VisitEntries(*this, visitor);
+ void FreeBlocks() {
+ mCount = 0;
+ mEntries.Clear();
}
void SelectPointers(CCGraphBuilder& aBuilder);
@@ -1105,73 +1130,27 @@ public:
bool aAsyncSnowWhiteFreeing,
CC_ForgetSkippableCallback aCb);
- MOZ_ALWAYS_INLINE nsPurpleBufferEntry* NewEntry()
- {
- if (MOZ_UNLIKELY(!mFreeList)) {
- PurpleBlock* b = new PurpleBlock;
- mFreeList = b->mEntries;
-
- // Add the new block as the second block in the list.
- b->mNext = mFirstBlock.mNext;
- mFirstBlock.mNext = b;
- }
-
- nsPurpleBufferEntry* e = mFreeList;
- mFreeList = (nsPurpleBufferEntry*)
- (uintptr_t(mFreeList->mNextInFreeList) & ~uintptr_t(1));
- return e;
- }
-
MOZ_ALWAYS_INLINE void Put(void* aObject, nsCycleCollectionParticipant* aCp,
- nsCycleCollectingAutoRefCnt* aRefCnt)
- {
- nsPurpleBufferEntry* e = NewEntry();
+ nsCycleCollectingAutoRefCnt* aRefCnt) {
+ nsPurpleBufferEntry entry(aObject, aRefCnt, aCp);
+ Unused << mEntries.Append(Move(entry));
+ MOZ_ASSERT(!entry.mRefCnt, "CC: PurpleBufferEntry:Put() Move failed!");
++mCount;
-
- e->mObject = aObject;
- e->mRefCnt = aRefCnt;
- e->mParticipant = aCp;
}
- void Remove(nsPurpleBufferEntry* aEntry)
- {
+ void Remove(nsPurpleBufferEntry* aEntry) {
MOZ_ASSERT(mCount != 0, "must have entries");
-
- if (aEntry->mRefCnt) {
- aEntry->mRefCnt->RemoveFromPurpleBuffer();
- aEntry->mRefCnt = nullptr;
- }
- aEntry->mNextInFreeList =
- (nsPurpleBufferEntry*)(uintptr_t(mFreeList) | uintptr_t(1));
- mFreeList = aEntry;
-
--mCount;
+ aEntry->Clear();
}
- uint32_t Count() const
- {
+ uint32_t Count() const {
return mCount;
}
- size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
- {
- size_t n = 0;
-
- // Don't measure mFirstBlock because it's within |this|.
- const PurpleBlock* block = mFirstBlock.mNext;
- while (block) {
- n += aMallocSizeOf(block);
- block = block->mNext;
- }
-
- // mFreeList is deliberately not measured because it points into
- // the purple buffer, which is within mFirstBlock and thus within |this|.
- //
- // We also don't measure the things pointed to by mEntries[] because
- // those pointers are non-owning.
-
- return n;
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mEntries.SizeOfExcludingThis(aMallocSizeOf);
}
};
@@ -1203,16 +1182,13 @@ private:
};
void
-nsPurpleBuffer::SelectPointers(CCGraphBuilder& aBuilder)
-{
+nsPurpleBuffer::SelectPointers(CCGraphBuilder& aBuilder) {
SelectPointersVisitor visitor(aBuilder);
VisitEntries(visitor);
NS_ASSERTION(mCount == 0, "AddPurpleRoot failed");
if (mCount == 0) {
FreeBlocks();
- InitBlocks();
- mFirstBlock.InitNextPointers();
}
}