This is an automated email from the ASF dual-hosted git repository.

ppa pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 270236c2ab IGNITE-23434 Sql. Provide system view for active locks 
(#4762)
270236c2ab is described below

commit 270236c2ab5dcb98ae2aa17bb41a21372bf78c5d
Author: Pavel Pereslegin <[email protected]>
AuthorDate: Mon Nov 25 16:46:39 2024 +0300

    IGNITE-23434 Sql. Provide system view for active locks (#4762)
---
 .../internal/sql/engine/ItLocksSystemViewTest.java | 127 +++++++++++++++++++++
 .../sql/engine/ItTransactionsSystemViewTest.java   |   2 +-
 .../org/apache/ignite/internal/tx/LockManager.java |   7 ++
 .../ignite/internal/tx/impl/HeapLockManager.java   |  35 ++++--
 .../ignite/internal/tx/impl/TxManagerImpl.java     |   9 +-
 .../internal/tx/views/LocksViewProvider.java       |  55 +++++++++
 .../{impl => views}/TransactionsViewProvider.java  |   6 +-
 7 files changed, 224 insertions(+), 17 deletions(-)

diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItLocksSystemViewTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItLocksSystemViewTest.java
new file mode 100644
index 0000000000..3014127ba7
--- /dev/null
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItLocksSystemViewTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.ignite.internal.sql.engine;
+
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.internal.sql.BaseSqlIntegrationTest;
+import org.apache.ignite.internal.sql.engine.util.MetadataMatcher;
+import org.apache.ignite.internal.tx.InternalTransaction;
+import org.apache.ignite.internal.tx.LockMode;
+import org.apache.ignite.sql.ColumnType;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * End-to-end tests to verify {@code LOCKS} system view.
+ */
+public class ItLocksSystemViewTest extends BaseSqlIntegrationTest {
+    private Set<String> nodeNames;
+
+    private Set<String> lockModes;
+
+    @Override
+    protected int initialNodes() {
+        return 2;
+    }
+
+    @BeforeAll
+    void beforeAll() {
+        await(systemViewManager().completeRegistration());
+
+        nodeNames = CLUSTER.runningNodes()
+                .map(Ignite::name)
+                .collect(Collectors.toSet());
+
+        lockModes = Arrays.stream(LockMode.values())
+                .map(Enum::name)
+                .collect(Collectors.toSet());
+    }
+
+    @Test
+    public void testMetadata() {
+        assertQuery("SELECT * FROM SYSTEM.LOCKS")
+                .columnMetadata(
+                        new 
MetadataMatcher().name("OWNING_NODE_ID").type(ColumnType.STRING).nullable(false),
+                        new 
MetadataMatcher().name("TX_ID").type(ColumnType.STRING).nullable(true),
+                        new 
MetadataMatcher().name("OBJECT_ID").type(ColumnType.STRING).nullable(true),
+                        new 
MetadataMatcher().name("MODE").type(ColumnType.STRING).nullable(true)
+                )
+                // RO tx doesn't take locks.
+                .returnNothing()
+                .check();
+    }
+
+    @Test
+    public void testData() {
+        Ignite node = CLUSTER.aliveNode();
+
+        sql("CREATE TABLE test (id INT PRIMARY KEY, val INT)");
+        sql("INSERT INTO test VALUES (0, 0), (2, 2)");
+
+        List<InternalTransaction> txs = List.of(
+                (InternalTransaction) node.transactions().begin(),
+                (InternalTransaction) node.transactions().begin(),
+                (InternalTransaction) node.transactions().begin()
+        );
+
+        try {
+            sql(txs.get(0), "INSERT INTO test VALUES (1, 1)");
+            sql(txs.get(1), "UPDATE test SET val = 1 WHERE id = 0");
+            sql(txs.get(2), "DELETE FROM test WHERE id = 2");
+
+            for (InternalTransaction tx : txs) {
+                List<List<Object>> rows = sql("SELECT * FROM SYSTEM.LOCKS 
WHERE TX_ID=?", tx.id().toString());
+
+                assertThat(rows, is(not(empty())));
+
+                for (List<Object> row : rows) {
+                    verifyLockInfo(row, tx.id().toString());
+                }
+            }
+        } finally {
+            txs.forEach(InternalTransaction::rollback);
+        }
+    }
+
+    private void verifyLockInfo(List<Object> row, String expectedTxId) {
+        int idx = 0;
+
+        String owningNode = (String) row.get(idx++);
+        String txId = (String) row.get(idx++);
+        String objectId = (String) row.get(idx++);
+        String mode = (String) row.get(idx);
+
+        assertThat(nodeNames, hasItem(owningNode));
+        assertThat(txId, equalTo(expectedTxId));
+        assertThat(objectId, not(emptyString()));
+        assertThat(lockModes, hasItem(mode));
+    }
+}
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItTransactionsSystemViewTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItTransactionsSystemViewTest.java
index 871dc41e98..7516e393bf 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItTransactionsSystemViewTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItTransactionsSystemViewTest.java
@@ -37,7 +37,7 @@ import org.apache.ignite.internal.tx.InternalTransaction;
 import org.apache.ignite.internal.tx.TransactionIds;
 import org.apache.ignite.internal.tx.TxPriority;
 import org.apache.ignite.internal.tx.impl.IgniteTransactionsImpl;
-import org.apache.ignite.internal.tx.impl.TransactionsViewProvider;
+import org.apache.ignite.internal.tx.views.TransactionsViewProvider;
 import org.apache.ignite.network.ClusterNode;
 import org.apache.ignite.sql.ColumnType;
 import org.apache.ignite.tx.Transaction;
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/LockManager.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/LockManager.java
index e696997f45..7360df6856 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/LockManager.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/LockManager.java
@@ -62,6 +62,13 @@ public interface LockManager extends 
EventProducer<LockEvent, LockEventParameter
      */
     void release(UUID txId, LockKey lockKey, LockMode lockMode);
 
+    /**
+     * Retrieves all locks.
+     *
+     * @return An iterator over a collection of locks.
+     */
+    Iterator<Lock> locks();
+
     /**
      * Retrieves all locks for the specified transaction id.
      *
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/HeapLockManager.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/HeapLockManager.java
index b74b9029bb..deaac3d07f 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/HeapLockManager.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/HeapLockManager.java
@@ -251,22 +251,18 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
         }
     }
 
+    @Override
+    public Iterator<Lock> locks() {
+        return txMap.entrySet().stream()
+                .flatMap(e -> collectLocksFromStates(e.getKey(), 
e.getValue()).stream())
+                .iterator();
+    }
+
     @Override
     public Iterator<Lock> locks(UUID txId) {
         ConcurrentLinkedQueue<Releasable> lockStates = txMap.get(txId);
 
-        List<Lock> result = new ArrayList<>();
-
-        if (lockStates != null) {
-            for (Releasable lockState : lockStates) {
-                Lock lock = lockState.lock(txId);
-                if (lock != null) {
-                    result.add(lock);
-                }
-            }
-        }
-
-        return result.iterator();
+        return collectLocksFromStates(txId, lockStates).iterator();
     }
 
     /**
@@ -366,6 +362,21 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
         });
     }
 
+    private static List<Lock> collectLocksFromStates(UUID txId, 
ConcurrentLinkedQueue<Releasable> lockStates) {
+        List<Lock> result = new ArrayList<>();
+
+        if (lockStates != null) {
+            for (Releasable lockState : lockStates) {
+                Lock lock = lockState.lock(txId);
+                if (lock != null) {
+                    result.add(lock);
+                }
+            }
+        }
+
+        return result;
+    }
+
     /**
      * Create lock exception with given parameters.
      *
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
index 281b3f6ae0..ff8ac82992 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
@@ -101,6 +101,8 @@ import org.apache.ignite.internal.tx.TxStateMetaFinishing;
 import org.apache.ignite.internal.tx.configuration.TransactionConfiguration;
 import 
org.apache.ignite.internal.tx.impl.TransactionInflights.ReadWriteTxContext;
 import org.apache.ignite.internal.tx.message.WriteIntentSwitchReplicatedInfo;
+import org.apache.ignite.internal.tx.views.LocksViewProvider;
+import org.apache.ignite.internal.tx.views.TransactionsViewProvider;
 import org.apache.ignite.internal.util.CompletableFutures;
 import org.apache.ignite.internal.util.ExceptionUtils;
 import org.apache.ignite.internal.util.IgniteSpinBusyLock;
@@ -935,7 +937,12 @@ public class TxManagerImpl implements TxManager, 
NetworkMessageHandler, SystemVi
 
     @Override
     public List<SystemView<?>> systemViews() {
-        return List.of(txViewProvider.get());
+        LocksViewProvider lockViewProvider = new 
LocksViewProvider(lockManager::locks);
+
+        return List.of(
+                txViewProvider.get(),
+                lockViewProvider.get()
+        );
     }
 
     static class TransactionFailureHandler {
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/views/LocksViewProvider.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/views/LocksViewProvider.java
new file mode 100644
index 0000000000..77687a1c8d
--- /dev/null
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/views/LocksViewProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.ignite.internal.tx.views;
+
+import static org.apache.ignite.internal.type.NativeTypes.stringOf;
+
+import org.apache.ignite.internal.systemview.api.SystemView;
+import org.apache.ignite.internal.systemview.api.SystemViews;
+import org.apache.ignite.internal.tx.Lock;
+import org.apache.ignite.internal.tx.LockKey;
+import org.apache.ignite.internal.util.SubscriptionUtils;
+
+/**
+ * {@code LOCKS} system view provider.
+ */
+public class LocksViewProvider {
+    /** Active locks. */
+    private final Iterable<Lock> locks;
+
+    public LocksViewProvider(Iterable<Lock> locks) {
+        this.locks = locks;
+    }
+
+    /** Returns system view exposing active locks. */
+    public SystemView<?> get() {
+        return SystemViews.<Lock>nodeViewBuilder()
+                .name("LOCKS")
+                .nodeNameColumnAlias("OWNING_NODE_ID")
+                .<String>addColumn("TX_ID", stringOf(36), lock -> 
lock.txId().toString())
+                .<String>addColumn("OBJECT_ID", stringOf(Short.MAX_VALUE), 
lock -> formatLockKey(lock.lockKey()))
+                .<String>addColumn("MODE", stringOf(2), lock -> 
lock.lockMode().name())
+                .dataProvider(SubscriptionUtils.fromIterable(locks))
+                .build();
+    }
+
+    // TODO https://issues.apache.org/jira/browse/IGNITE-23755 Provide more 
user-friendly information about locked objects
+    private String formatLockKey(LockKey lockKey) {
+        return lockKey.toString();
+    }
+}
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionsViewProvider.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/views/TransactionsViewProvider.java
similarity index 98%
rename from 
modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionsViewProvider.java
rename to 
modules/transactions/src/main/java/org/apache/ignite/internal/tx/views/TransactionsViewProvider.java
index 52f0f199ed..f5b34d7004 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionsViewProvider.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/views/TransactionsViewProvider.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.tx.impl;
+package org.apache.ignite.internal.tx.views;
 
 import static org.apache.ignite.internal.tx.TxState.isFinalState;
 import static org.apache.ignite.internal.type.NativeTypes.stringOf;
@@ -50,7 +50,7 @@ public class TransactionsViewProvider {
     private volatile Iterable<TxInfo> dataSource;
 
     /** Initializes provider with data sources. */
-    void init(
+    public void init(
             UUID localNodeId,
             Collection<UUID> roTxIds,
             Map<UUID, TxStateMeta> rwTxStates
@@ -63,7 +63,7 @@ public class TransactionsViewProvider {
     }
 
     /** Returns a {@code TRANSACTIONS} system view. */
-    SystemView<?> get() {
+    public SystemView<?> get() {
         Publisher<TxInfo> dataProvider = SubscriptionUtils.fromIterable(
                 () -> {
                     Iterable<TxInfo> dataSource0 = dataSource;

Reply via email to