Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion etc/launch-mongodb-local/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ services:
entrypoint: ["bash", "-c", "cp /run/key-source /tmp/mongodb-keyfile && chown mongodb:mongodb /tmp/mongodb-keyfile && chmod 400 /tmp/mongodb-keyfile && exec /usr/local/bin/docker-entrypoint.sh \"$$@\"", "--"]
command: ["mongod", "--replSet", "rs0", "--bind_ip_all", "--auth", "--keyFile", "/tmp/mongodb-keyfile"]
ports:
- "127.0.0.1:${MONGODB_PORT}:27017"
- "127.0.0.1:${MONGODB_PORT:-}:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGODB_ROOT_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGODB_ROOT_PASSWORD}
Expand Down
13 changes: 13 additions & 0 deletions etc/launch-mongodb-local/init_and_seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,16 @@ source.events.insertMany([
{_id: ObjectId("64b000000000000000000005"), device_id: "device-002", site_id: "site-east", ts: ISODate("2026-07-27T10:01:00Z"), measurement: "malformed"}
]);
source.events.createIndex({ts: 1, _id: 1});

source.temporal_edges.drop();
source.temporal_edges.insertOne({
_id: ObjectId("64b000000000000000000101"),
ts: ISODate("2026-07-27T10:00:05.100Z")
});
source.temporal_edges.createIndex({ts: 1});

source.decoded_budget.drop();
source.decoded_budget.insertOne({
_id: ObjectId("64b000000000000000000201"),
payload: "x".repeat(192 * 1024)
});
43 changes: 28 additions & 15 deletions optools/mongodb_ci.bash
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,6 @@ cleanup() {
return "$status"
}

free_port() {
python3 - <<'PY'
import socket
s = socket.socket()
s.bind(("127.0.0.1", 0))
print(s.getsockname()[1])
s.close()
PY
}

wait_mongo() {
local deadline=$((SECONDS + 120))
until docker compose -p "$COMPOSE_PROJECT_NAME" -f "$ROOT_DIR/etc/launch-mongodb-local/compose.yaml" exec -T mongo \
Expand All @@ -91,6 +81,22 @@ wait_mongo() {
done
}

wait_mo_port() {
local deadline=$((SECONDS + 120)) port=""
while (( SECONDS < deadline )); do
if [[ -n "$MO_PID" ]] && ! kill -0 "$MO_PID" >/dev/null 2>&1; then
die "MatrixOne exited before publishing its frontend listener"
fi
port="$(sed -nE 's/.*Server Listening on : [^ ]*:([0-9]+).*/\1/p' "$TMP_DIR/mo-service.log" | tail -1)"
if [[ "$port" =~ ^[1-9][0-9]*$ ]]; then
printf '%s\n' "$port"
return
fi
sleep 0.25
done
die "MatrixOne did not publish its frontend listener"
}

wait_primary() {
local deadline=$((SECONDS + 120))
until docker compose -p "$COMPOSE_PROJECT_NAME" -f "$ROOT_DIR/etc/launch-mongodb-local/compose.yaml" exec -T mongo \
Expand Down Expand Up @@ -124,7 +130,7 @@ generate_mo_config() {
printf '\n[cn.frontend.mongodb]\n'
printf 'enable = true\nallow-loopback = true\n'
printf 'connect-timeout = "10s"\nserver-selection-timeout = "10s"\nsocket-timeout = "30s"\n'
printf 'batch-rows = 2\nmax-source-concurrency = 2\n'
printf 'batch-rows = 2\nmax-batch-bytes = 1048576\nmax-value-bytes = 524288\nmax-source-concurrency = 2\n'
} >>"$generated_dir/cn.toml"
sed -e "s#\./etc/launch/log.toml#$generated_dir/log.toml#" \
-e "s#\./etc/launch/tn.toml#$generated_dir/tn.toml#" \
Expand All @@ -138,7 +144,10 @@ run_e2e() {
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/mo-mongodb-e2e.XXXXXX")"
trap cleanup EXIT
export COMPOSE_PROJECT_NAME="mo-mongodb-$(basename "$TMP_DIR" | tr '[:upper:].' '[:lower:]-')"
export MONGODB_PORT="$(free_port)" MO_PORT="$(free_port)"
# Let Docker and MatrixOne bind port 0 themselves. The selected listeners
# stay owned from allocation through use, eliminating the bind-close-rebind
# window that let adjacent CI jobs steal either port.
export MONGODB_PORT="" MO_PORT="0"
export MONGODB_ROOT_USER="root_$(openssl rand -hex 6)"
export MONGODB_ROOT_PASSWORD="$(openssl rand -hex 24)"
export MONGODB_READER_PASSWORD="$(openssl rand -hex 24)"
Expand All @@ -147,7 +156,12 @@ run_e2e() {
openssl rand -base64 756 >"$MONGODB_KEYFILE"
chmod 600 "$MONGODB_KEYFILE"

(cd "$ROOT_DIR" && make build)
generate_mo_config
docker compose -p "$COMPOSE_PROJECT_NAME" -f "$ROOT_DIR/etc/launch-mongodb-local/compose.yaml" up -d
MONGODB_PORT="$(docker compose -p "$COMPOSE_PROJECT_NAME" -f "$ROOT_DIR/etc/launch-mongodb-local/compose.yaml" port mongo 27017 | sed -nE 's/.*:([0-9]+)$/\1/p' | tail -1)"
[[ "$MONGODB_PORT" =~ ^[1-9][0-9]*$ ]] || die "Docker did not publish the MongoDB listener"
export MONGODB_PORT
wait_mongo
docker compose -p "$COMPOSE_PROJECT_NAME" -f "$ROOT_DIR/etc/launch-mongodb-local/compose.yaml" exec -T mongo \
mongosh --quiet -u "$MONGODB_ROOT_USER" -p "$MONGODB_ROOT_PASSWORD" --authenticationDatabase admin \
Expand All @@ -158,9 +172,6 @@ run_e2e() {
-e MONGODB_READER_NEXT_PASSWORD="$MONGODB_READER_NEXT_PASSWORD" mongo \
mongosh --quiet -u "$MONGODB_ROOT_USER" -p "$MONGODB_ROOT_PASSWORD" --authenticationDatabase admin \
<"$ROOT_DIR/etc/launch-mongodb-local/init_and_seed.js" >/dev/null

(cd "$ROOT_DIR" && make build)
generate_mo_config
export MO_MONGODB_E2E_CREDENTIAL="{\"Username\":\"mo_reader\",\"Password\":\"$MONGODB_READER_PASSWORD\"}"
export MO_MONGODB_E2E_CREDENTIAL_NEXT="{\"Username\":\"mo_reader_next\",\"Password\":\"$MONGODB_READER_NEXT_PASSWORD\"}"
if [[ "$(uname -s)" == Darwin ]]; then
Expand All @@ -170,6 +181,8 @@ run_e2e() {
fi
"$ROOT_DIR/mo-service" -launch "$TMP_DIR/mo-config/launch.toml" >"$TMP_DIR/mo-service.log" 2>&1 &
MO_PID=$!
MO_PORT="$(wait_mo_port)"
export MO_PORT
(cd "$ROOT_DIR" && go run ./test/mongodb/mongodb_e2e_local.go \
--dsn "root:111@tcp(127.0.0.1:$MO_PORT)/?timeout=5s&readTimeout=30s&writeTimeout=30s" \
--mongo-host "127.0.0.1:$MONGODB_PORT" --report-dir "$REPORT_DIR")
Expand Down
24 changes: 17 additions & 7 deletions pkg/cnservice/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1168,17 +1168,23 @@ func (s *service) initMongoDBRuntime() {
MaxConversionErrors: parameters.MaxConversionErrors, MaxConversionErrorRate: parameters.MaxConversionErrorRate,
MaxSourceConcurrency: parameters.MaxSourceConcurrency,
}
pool := sqlmongodb.NewValidatedClientPool(
sqlmongodb.OfficialClientFactory{},
sqlmongodb.CatalogConnectionResolver{Executor: s.sqlExecutor},
config.MaxCachedClients,
)
retirements := sqlmongodb.NewClientRetirementQueue(
pool,
sqlmongodb.ClusterRemoteClientRetirer{Cluster: s.moCluster, QueryClient: s.queryClient},
sqlmongodb.DefaultClientRetirementQueueCapacity,
)
dependencies := &sqlmongodb.RuntimeDependencies{
Config: config,
Connections: sqlmongodb.CatalogConnectionResolver{Executor: s.sqlExecutor},
Mappings: sqlmongodb.CatalogMappingResolver{Executor: s.sqlExecutor},
Secrets: sqlmongodb.EnvSecretResolver{},
Pool: sqlmongodb.NewValidatedClientPool(
sqlmongodb.OfficialClientFactory{},
sqlmongodb.CatalogConnectionResolver{Executor: s.sqlExecutor},
config.MaxCachedClients,
),
Limiter: sqlmongodb.NewSourceLimiter(config.MaxSourceConcurrency),
Pool: pool, Limiter: sqlmongodb.NewSourceLimiter(config.MaxSourceConcurrency),
Retirements: retirements,
}
runtime.ServiceRuntime(s.cfg.UUID).SetGlobalVariables(sqlmongodb.RuntimeDependenciesKey, dependencies)
}
Expand All @@ -1195,7 +1201,11 @@ func (s *service) closeMongoDBRuntime() error {
}
ctx, cancel := context.WithTimeoutCause(context.Background(), 10*time.Second, moerr.CauseShutdown)
defer cancel()
return dependencies.Pool.Close(ctx)
var err error
if dependencies.Retirements != nil {
err = dependencies.Retirements.Close(ctx)
}
return errors.Join(err, dependencies.Pool.Close(ctx))
}

func (s *service) initIncrService() {
Expand Down
3 changes: 3 additions & 0 deletions pkg/common/moerr/cause.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ var (
CauseBuildInsertIndexMetaBatch2 = NewInternalError(context.Background(), "buildInsertIndexMetaBatch 2")
//pkg/sql/colexec/dispatch
CauseWaitRemoteRegsReady = NewInternalError(context.Background(), "waitRemoteRegsReady")
//pkg/sql/mongodb
CauseMongoDBClientCleanup = NewInternalError(context.Background(), "mongodb client cleanup")
CauseMongoDBClientRetirement = NewInternalError(context.Background(), "mongodb client retirement")
//pkg/sql/compile
CauseIsAvailable = NewInternalError(context.Background(), "isAvailable")
CauseNewMessageSenderOnClient = NewInternalError(context.Background(), "newMessageSenderOnClient")
Expand Down
3 changes: 3 additions & 0 deletions pkg/common/moerr/cause_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ var causeArray = []error{

CauseWaitRemoteRegsReady,

CauseMongoDBClientCleanup,
CauseMongoDBClientRetirement,

CauseIsAvailable,
CauseNewMessageSenderOnClient,
CauseWaitingTheStopResponse,
Expand Down
10 changes: 8 additions & 2 deletions pkg/frontend/mongodb.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,14 @@ func handleDropMongoDBConnection(ctx context.Context, ses *Session, stmt *tree.D
}

func retireMongoDBClients(ctx context.Context, service string, retirement mongodb.ClientRetirement) {
if dependencies := mongoDBRuntimeDependencies(service); dependencies != nil && dependencies.Pool != nil {
_ = retirement.Apply(dependencies.Pool)
if dependencies := mongoDBRuntimeDependencies(service); dependencies != nil {
if dependencies.Retirements != nil {
dependencies.Retirements.Submit(retirement)
return
}
if dependencies.Pool != nil {
_ = retirement.Apply(dependencies.Pool)
}
}
pu := getPuIfPresent(service)
if pu == nil || pu.QueryClient == nil {
Expand Down
6 changes: 5 additions & 1 deletion pkg/frontend/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ func (mo *MOServer) GetRoutineManager() *RoutineManager {
}

func (mo *MOServer) Start() error {
logutil.Infof("Server Listening on : %s ", mo.addr)
address := mo.addr
if len(mo.listeners) > 0 && mo.listeners[0] != nil {
address = mo.listeners[0].Addr().String()
}
logutil.Infof("Server Listening on : %s ", address)
mo.running = true
mo.startTempTableGC(24 * time.Hour)
mo.startConnectionLivenessMonitor()
Expand Down
8 changes: 8 additions & 0 deletions pkg/frontend/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
indexplugin "github.com/matrixorigin/matrixone/pkg/indexplugin"
pbplan "github.com/matrixorigin/matrixone/pkg/pb/plan"
"github.com/matrixorigin/matrixone/pkg/pb/timestamp"
sqlmongodb "github.com/matrixorigin/matrixone/pkg/sql/mongodb"
"github.com/matrixorigin/matrixone/pkg/sql/parsers"
"github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect"
"github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect/mysql"
Expand Down Expand Up @@ -132,6 +133,13 @@ var (

catalog.MOPartitionMetadata: 1,
catalog.MOPartitionTables: 1,

// MongoDB external tables are deliberately skipped by bulk restore.
// Their table-ID keyed mappings must follow the same policy; cloning a
// historical row without its external table creates an orphan that can
// permanently block DROP MONGODB CONNECTION. Snapshot and PITR share this
// system-table policy.
sqlmongodb.TableMappings: 1,
}
)

Expand Down
10 changes: 10 additions & 0 deletions pkg/frontend/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/matrixorigin/matrixone/pkg/defines"
mock_frontend "github.com/matrixorigin/matrixone/pkg/frontend/test"
"github.com/matrixorigin/matrixone/pkg/pb/txn"
sqlmongodb "github.com/matrixorigin/matrixone/pkg/sql/mongodb"
"github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect"
"github.com/matrixorigin/matrixone/pkg/sql/parsers/tree"
"github.com/matrixorigin/matrixone/pkg/sql/plan"
Expand Down Expand Up @@ -83,6 +84,15 @@ func TestGetFkDepsFromTableInfos(t *testing.T) {
require.NotContains(t, deps, genKey("d", "v"))
}

func TestMongoDBMappingsFollowExternalTableRestoreSkipPolicy(t *testing.T) {
info := &tableInfo{dbName: moCatalog, tblName: sqlmongodb.TableMappings, typ: "BASE TABLE"}
for _, accountID := range []uint32{sysAccountID, 7} {
require.True(t, needSkipTable(accountID, moCatalog, sqlmongodb.TableMappings))
require.True(t, needSkipSystemTable(accountID, info))
}
require.Equal(t, int8(1), needSkipTablesInMocatalog[sqlmongodb.TableMappings])
}

func TestMergeFkDepsDeduplicatesSources(t *testing.T) {
child := genKey("d", "child")
parent := genKey("d", "parent")
Expand Down
Loading
Loading