mirror of
https://gitee.com/johng/gf
synced 2026-06-08 02:27:42 +08:00
Compare commits
34 Commits
copilot/fi
...
v2.9.7
| Author | SHA1 | Date | |
|---|---|---|---|
| dd62b18877 | |||
| cb4681ce3e | |||
| 7daf916032 | |||
| c82da1e57c | |||
| 90564f9fb0 | |||
| 4d6c7e3d3a | |||
| 18e77de02f | |||
| bf6238e178 | |||
| 887a776441 | |||
| 7274a7399a | |||
| b59824e9dc | |||
| 5cbe421aaa | |||
| 852c3dda62 | |||
| d8b857f930 | |||
| d353bf0fbc | |||
| baf30a0e99 | |||
| 6e0ba551f9 | |||
| 1650aab340 | |||
| bb9133ab9d | |||
| 48845c3473 | |||
| ea956189bf | |||
| 3912d97811 | |||
| 50fb349bc9 | |||
| 777d7aabb5 | |||
| 5a67aac85d | |||
| 132a5ab9a3 | |||
| 8575f01273 | |||
| ac75026716 | |||
| 485a9637cc | |||
| b57b49ecca | |||
| cdead46c79 | |||
| a4883e6e3d | |||
| fe8ba5e35f | |||
| 54b7c249fd |
29
.github/workflows/ci-main.yml
vendored
29
.github/workflows/ci-main.yml
vendored
@ -54,7 +54,7 @@ jobs:
|
||||
# Service containers to run with `code-test`
|
||||
services:
|
||||
# Etcd service.
|
||||
# docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnamilegacy/etcd:3.4.24
|
||||
# docker run -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnamilegacy/etcd:3.4.24
|
||||
etcd:
|
||||
image: bitnamilegacy/etcd:3.4.24
|
||||
env:
|
||||
@ -75,7 +75,7 @@ jobs:
|
||||
- 6379:6379
|
||||
|
||||
# MySQL backend server.
|
||||
# docker run -d --name mysql \
|
||||
# docker run \
|
||||
# -p 3306:3306 \
|
||||
# -e MYSQL_DATABASE=test \
|
||||
# -e MYSQL_ROOT_PASSWORD=12345678 \
|
||||
@ -89,7 +89,7 @@ jobs:
|
||||
- 3306:3306
|
||||
|
||||
# MariaDb backend server.
|
||||
# docker run -d --name mariadb \
|
||||
# docker run \
|
||||
# -p 3307:3306 \
|
||||
# -e MYSQL_DATABASE=test \
|
||||
# -e MYSQL_ROOT_PASSWORD=12345678 \
|
||||
@ -103,7 +103,7 @@ jobs:
|
||||
- 3307:3306
|
||||
|
||||
# PostgreSQL backend server.
|
||||
# docker run -d --name postgres \
|
||||
# docker run \
|
||||
# -p 5432:5432 \
|
||||
# -e POSTGRES_PASSWORD=12345678 \
|
||||
# -e POSTGRES_USER=postgres \
|
||||
@ -150,7 +150,7 @@ jobs:
|
||||
--health-retries 10
|
||||
|
||||
# ClickHouse backend server.
|
||||
# docker run -d --name clickhouse \
|
||||
# docker run \
|
||||
# -p 9000:9000 -p 8123:8123 -p 9001:9001 \
|
||||
# clickhouse/clickhouse-server:24.11.1.2557-alpine
|
||||
clickhouse-server:
|
||||
@ -161,7 +161,7 @@ jobs:
|
||||
- 9001:9001
|
||||
|
||||
# Polaris backend server.
|
||||
# docker run -d --name polaris \
|
||||
# docker run \
|
||||
# -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \
|
||||
# polarismesh/polaris-standalone:v1.17.2
|
||||
polaris:
|
||||
@ -198,6 +198,17 @@ jobs:
|
||||
ports:
|
||||
- 5236:5236
|
||||
|
||||
# openGauss server
|
||||
# docker run --privileged=true -e GS_PASSWORD=UTpass@1234 -p 9950:5432 opengauss/opengauss:7.0.0-RC1.B023
|
||||
gaussdb:
|
||||
image: opengauss/opengauss:7.0.0-RC1.B023
|
||||
env:
|
||||
GS_PASSWORD: UTpass@1234
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- 9950:5432
|
||||
|
||||
|
||||
zookeeper:
|
||||
image: zookeeper:3.8
|
||||
ports:
|
||||
@ -221,6 +232,12 @@ jobs:
|
||||
detached: true
|
||||
limit-access-to-actor: false
|
||||
|
||||
- name: Free Disk Space
|
||||
run: |
|
||||
df -h /
|
||||
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/hostedtoolcache/CodeQL /opt/hostedtoolcache/Python || true
|
||||
df -h /
|
||||
|
||||
- name: Start Apollo Containers
|
||||
run: docker compose -f ".github/workflows/apollo/docker-compose.yml" up -d --build
|
||||
|
||||
|
||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -17,11 +17,12 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Github Code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
|
||||
- name: Set Up Golang Environment
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.25
|
||||
cache: false
|
||||
|
||||
- name: Build CLI Binary
|
||||
run: |
|
||||
|
||||
0
.github/workflows/scripts/before_script.sh
vendored
Normal file → Executable file
0
.github/workflows/scripts/before_script.sh
vendored
Normal file → Executable file
250
.github/workflows/scripts/ci-main-clean.sh
vendored
Executable file
250
.github/workflows/scripts/ci-main-clean.sh
vendored
Executable file
@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
dirpath=$1
|
||||
|
||||
# Extract the base directory name for pattern matching
|
||||
if [ -n "$dirpath" ]; then
|
||||
dirname=$(basename "$dirpath")
|
||||
echo "Cleaning Docker resources for path: $dirpath (pattern: $dirname)"
|
||||
df -h /
|
||||
|
||||
# Process containers and images based on the directory
|
||||
case "$dirname" in
|
||||
# "mysql")
|
||||
# echo "Cleaning mysql resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing mysql containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q mysql 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
"mssql")
|
||||
echo "Cleaning mssql resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing mssql containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q mcr.microsoft.com/mssql/server 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
"pgsql")
|
||||
echo "Cleaning postgres resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing postgres containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q postgres 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
"oracle")
|
||||
echo "Cleaning oracle resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing oracle containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q loads/oracle-xe-11g-r2 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
"dm")
|
||||
echo "Cleaning dm resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing dm containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q loads/dm 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
"clickhouse")
|
||||
echo "Cleaning clickhouse resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing clickhouse containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q clickhouse/clickhouse-server 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
# "redis")
|
||||
# echo "Cleaning redis resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing redis containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q redis loads/redis loads/redis-sentinel 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
"etcd")
|
||||
echo "Cleaning etcd resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing etcd containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q bitnamilegacy/etcd 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
# "consul")
|
||||
# echo "Cleaning consul resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing consul containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q consul 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
# "nacos")
|
||||
# echo "Cleaning nacos resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing nacos containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q nacos/nacos-server 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
# "polaris")
|
||||
# echo "Cleaning polaris resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing polaris containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q polarismesh/polaris-standalone 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
"zookeeper")
|
||||
echo "Cleaning zookeeper resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing zookeeper containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q zookeeper 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
# "apollo")
|
||||
# echo "Cleaning apollo resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing apollo containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q loads/apollo-quick-start 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
*)
|
||||
# No matching pattern, skip cleanup
|
||||
echo "No specific Docker cleanup rule for '$dirname', skipping cleanup"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove dangling images and volumes to free up space
|
||||
echo "Removing dangling images and unused volumes..."
|
||||
docker image prune -f 2>/dev/null || true
|
||||
docker volume prune -f 2>/dev/null || true
|
||||
|
||||
echo "Docker cleanup completed for $dirname"
|
||||
docker system df
|
||||
df -h /
|
||||
fi
|
||||
|
||||
# df -h /
|
||||
# Filesystem Size Used Avail Use% Mounted on
|
||||
# /dev/root 72G 67G 5.4G 93% /
|
||||
# tmpfs 7.9G 84K 7.9G 1% /dev/shm
|
||||
# tmpfs 3.2G 2.6M 3.2G 1% /run
|
||||
# tmpfs 5.0M 0 5.0M 0% /run/lock
|
||||
# /dev/sdb16 881M 62M 758M 8% /boot
|
||||
# /dev/sdb15 105M 6.2M 99M 6% /boot/efi
|
||||
# /dev/sda1 74G 4.1G 66G 6% /mnt
|
||||
# tmpfs 1.6G 12K 1.6G 1% /run/user/1001
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ docker system df
|
||||
# TYPE TOTAL ACTIVE SIZE RECLAIMABLE
|
||||
# Images 18 11 8.326GB 1.644GB (19%)
|
||||
# Containers 11 11 2.692GB 0B (0%)
|
||||
# Local Volumes 11 8 665.7MB 211.9MB (31%)
|
||||
# Build Cache 0 0 0B 0B
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ docker images
|
||||
# REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
# alpine/curl latest 99fd43792a61 2 days ago 13.5MB
|
||||
# postgres 17-alpine b6bf692a8125 9 days ago 278MB
|
||||
# zookeeper 3.8 2f26c02b94ca 10 days ago 306MB
|
||||
# mariadb 11.4 063fb6684f96 10 days ago 332MB
|
||||
# mcr.microsoft.com/mssql/server 2022-latest a2fbff321505 4 weeks ago 1.61GB
|
||||
# clickhouse/clickhouse-server 24.11.1.2557-alpine 2eee9fd3ae74 12 months ago 539MB
|
||||
# redis 7.0 7705dd2858c1 18 months ago 109MB
|
||||
# consul 1.15 686495461132 20 months ago 155MB
|
||||
# mysql 5.7 5107333e08a8 23 months ago 501MB
|
||||
# polarismesh/polaris-standalone v1.17.2 b7a8cf0a8438 2 years ago 545MB
|
||||
# bitnamilegacy/etcd 3.4.24 74ae5e205ac5 2 years ago 134MB
|
||||
# nacos/nacos-server v2.1.2 a978644d9246 2 years ago 1.06GB
|
||||
# loads/redis 7.0-sentinel 6f12d40540ba 3 years ago 114MB
|
||||
# loads/dm v8.1.2.128_ent_x86_64_ctm_pack4 ccb727ce9dce 3 years ago 432MB
|
||||
# loads/redis-sentinel 7.0 6818c626f5ca 3 years ago 104MB
|
||||
# loads/apollo-quick-start latest 8490de672148 3 years ago 190MB
|
||||
# alpine 3.8 c8bccc0af957 5 years ago 4.41MB
|
||||
# loads/oracle-xe-11g-r2 11.2.0 0d19fd2e072e 6 years ago 2.1GB
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ docker ps -s
|
||||
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
|
||||
# 8214f83420c6 zookeeper:3.8 "/docker-entrypoint.…" 6 minutes ago Up 6 minutes 2888/tcp, 3888/tcp, 0.0.0.0:2181->2181/tcp, [::]:2181->2181/tcp, 8080/tcp d66bac92ae9646f688f70ed4b5176f14_zookeeper38_3a22ef 33kB (virtual 306MB)
|
||||
# 8938d73842e8 loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4 "/bin/bash /opt/star…" 6 minutes ago Up 6 minutes 0.0.0.0:5236->5236/tcp, [::]:5236->5236/tcp ca280fbdb86f40c2acf86d7d526c6285_loadsdmv812128_ent_x86_64_ctm_pack4_770a59 844MB (virtual 1.28GB)
|
||||
# 0d3a653fe1f2 loads/oracle-xe-11g-r2:11.2.0 "/bin/sh -c '/usr/sb…" 6 minutes ago Up 6 minutes 22/tcp, 8080/tcp, 0.0.0.0:1521->1521/tcp, [::]:1521->1521/tcp 2048856d428c4967b1c35193eb8c9192_loadsoraclexe11gr21120_295d54 1.3GB (virtual 3.4GB)
|
||||
# ca3936189166 polarismesh/polaris-standalone:v1.17.2 "/bin/bash run.sh" 6 minutes ago Up 6 minutes 0.0.0.0:8090-8091->8090-8091/tcp, [::]:8090-8091->8090-8091/tcp, 8080/tcp, 8100-8101/tcp, 0.0.0.0:8093->8093/tcp, [::]:8093->8093/tcp, 8761/tcp, 15010/tcp, 0.0.0.0:9090-9091->9090-9091/tcp, [::]:9090-9091->9090-9091/tcp cbd43dceef754e2d8aab507e33167be7_polarismeshpolarisstandalonev1172_ca40b6 299MB (virtual 844MB)
|
||||
# 26169dad485e clickhouse/clickhouse-server:24.11.1.2557-alpine "/entrypoint.sh" 6 minutes ago Up 6 minutes 0.0.0.0:8123->8123/tcp, [::]:8123->8123/tcp, 0.0.0.0:9000-9001->9000-9001/tcp, [::]:9000-9001->9000-9001/tcp, 9009/tcp f1c7766fbe36401792a6f735d7acf123_clickhouseclickhouseserver241112557alpine_cfc034 338kB (virtual 539MB)
|
||||
# 04689a1d581f mcr.microsoft.com/mssql/server:2022-latest "/opt/mssql/bin/laun…" 6 minutes ago Up 6 minutes (healthy) 0.0.0.0:1433->1433/tcp, [::]:1433->1433/tcp 41d685349a7640b28230db8d0f60efe7_mcrmicrosoftcommssqlserver2022latest_fe29fb 108MB (virtual 1.72GB)
|
||||
# d5fbc5f811af postgres:17-alpine "docker-entrypoint.s…" 6 minutes ago Up 6 minutes (healthy) 0.0.0.0:5432->5432/tcp, [::]:5432->5432/tcp 2783be71b5ce417ab9a31428e7b4d8f2_postgres17alpine_c60840 63B (virtual 278MB)
|
||||
# da96a7ad7a01 mariadb:11.4 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 0.0.0.0:3307->3306/tcp, [::]:3307->3306/tcp 45eed646fa6c4a698893ee11cda95a4c_mariadb114_3a9cd6 2B (virtual 332MB)
|
||||
# 27ba1904ba3a mysql:5.7 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp, 33060/tcp ea6d7a4c207d427a95b5ae0db91fdf56_mysql57_c21053 4B (virtual 501MB)
|
||||
# 518e785d1bb6 redis:7.0 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes (healthy) 0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp af6044fc849e441bbc6c48f7a5ec5fec_redis70_b11994 0B (virtual 109MB)
|
||||
# 7495ec2cd8e3 bitnamilegacy/etcd:3.4.24 "/opt/bitnami/script…" 7 minutes ago Up 7 minutes 0.0.0.0:2379->2379/tcp, [::]:2379->2379/tcp, 2380/tcp 49f2a2a6bf3a4fae842cc950bbc3658a_bitnamilegacyetcd3424_1265e1 145MB (virtual 279MB)
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ du -ah --max-depth=1 /usr | sort -n
|
||||
# 4.0K /usr/games
|
||||
# 4.0K /usr/lib64
|
||||
# 6.6G /usr/lib
|
||||
# 9.3G /usr/share
|
||||
# 15M /usr/lib32
|
||||
# 24G /usr/local
|
||||
# 41G /usr
|
||||
# 95M /usr/sbin
|
||||
# 156M /usr/include
|
||||
# 158M /usr/src
|
||||
# 402M /usr/libexec
|
||||
# 841M /usr/bin
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ du -ah --max-depth=1 /opt | sort -n
|
||||
# 4.0K /opt/pipx_bin
|
||||
# 5.8G /opt/hostedtoolcache
|
||||
# 8.5G /opt
|
||||
# 12K /opt/containerd
|
||||
# 14M /opt/hca
|
||||
# 16K /opt/post-generation
|
||||
# 217M /opt/runner-cache
|
||||
# 243M /opt/actionarchivecache
|
||||
# 374M /opt/google
|
||||
# 515M /opt/pipx
|
||||
# 655M /opt/az
|
||||
# 783M /opt/microsoft
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ du -ah --max-depth=1 /opt/hostedtoolcache/ | sort -n
|
||||
# 1.1G /opt/hostedtoolcache/go
|
||||
# 1.6G /opt/hostedtoolcache/CodeQL
|
||||
# 1.9G /opt/hostedtoolcache/Python
|
||||
# 5.8G /opt/hostedtoolcache/
|
||||
# 9.9M /opt/hostedtoolcache/protoc
|
||||
# 24K /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk
|
||||
# 217M /opt/hostedtoolcache/Ruby
|
||||
# 520M /opt/hostedtoolcache/PyPy
|
||||
# 574M /opt/hostedtoolcache/node
|
||||
|
||||
54
.github/workflows/scripts/ci-main.sh
vendored
Normal file → Executable file
54
.github/workflows/scripts/ci-main.sh
vendored
Normal file → Executable file
@ -6,56 +6,54 @@ coverage=$1
|
||||
for file in `find . -name go.mod`; do
|
||||
dirpath=$(dirname $file)
|
||||
echo $dirpath
|
||||
|
||||
# ignore mssql tests as its docker service failed
|
||||
# TODO remove this ignoring codes after the mssql docker service OK
|
||||
if [ "mssql" = $(basename $dirpath) ]; then
|
||||
continue 1
|
||||
fi
|
||||
|
||||
|
||||
# package kubecm was moved to sub ci procedure.
|
||||
if [ "kubecm" = $(basename $dirpath) ]; then
|
||||
continue 1
|
||||
fi
|
||||
|
||||
|
||||
# examples directory was moved to sub ci procedure.
|
||||
if [[ $dirpath =~ "/examples/" ]]; then
|
||||
continue 1
|
||||
fi
|
||||
|
||||
|
||||
if [[ $file =~ "/testdata/" ]]; then
|
||||
echo "ignore testdata path $file"
|
||||
continue 1
|
||||
fi
|
||||
|
||||
# Check if it's a contrib directory
|
||||
if [[ $dirpath =~ "/contrib/" ]]; then
|
||||
# Check if go version meets the requirement
|
||||
if ! go version | grep -qE "go${LATEST_GO_VERSION}"; then
|
||||
echo "ignore path $dirpath as go version is not ${LATEST_GO_VERSION}: $(go version)"
|
||||
# clean docker containers and images to free disk space
|
||||
# bash .github/workflows/scripts/ci-main-clean.sh "$dirpath"
|
||||
continue 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $file =~ "/testdata/" ]]; then
|
||||
echo "ignore testdata path $file"
|
||||
continue 1
|
||||
fi
|
||||
|
||||
if [[ $dirpath = "." ]]; then
|
||||
# No space left on device error sometimes occurs in CI pipelines, so clean the cache before tests.
|
||||
go clean -cache
|
||||
fi
|
||||
|
||||
|
||||
# if [[ $dirpath = "." ]]; then
|
||||
# # No space left on device error sometimes occurs in CI pipelines, so clean the cache before tests.
|
||||
# go clean -cache
|
||||
# fi
|
||||
|
||||
cd $dirpath
|
||||
go mod tidy
|
||||
go build ./...
|
||||
|
||||
|
||||
# test with coverage
|
||||
if [ "${coverage}" = "coverage" ]; then
|
||||
go test ./... -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./...,github.com/gogf/gf/... || exit 1
|
||||
|
||||
if grep -q "/gogf/gf/.*/v2" go.mod; then
|
||||
sed -i "s/gogf\/gf\(\/.*\)\/v2/gogf\/gf\/v2\1/g" coverage.out
|
||||
fi
|
||||
go test ./... -count=1 -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./...,github.com/gogf/gf/... || exit 1
|
||||
|
||||
if grep -q "/gogf/gf/.*/v2" go.mod; then
|
||||
sed -i "s/gogf\/gf\(\/.*\)\/v2/gogf\/gf\/v2\1/g" coverage.out
|
||||
fi
|
||||
else
|
||||
go test ./... -race || exit 1
|
||||
go test ./... -count=1 -race || exit 1
|
||||
fi
|
||||
|
||||
|
||||
cd -
|
||||
# clean docker containers and images to free disk space
|
||||
# bash .github/workflows/scripts/ci-main-clean.sh "$dirpath"
|
||||
done
|
||||
|
||||
0
.github/workflows/scripts/ci-sub.sh
vendored
Normal file → Executable file
0
.github/workflows/scripts/ci-sub.sh
vendored
Normal file → Executable file
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "examples"]
|
||||
path = examples
|
||||
url = git@github.com:gogf/examples.git
|
||||
|
||||
1
Makefile
1
Makefile
@ -6,6 +6,7 @@ tidy:
|
||||
./.make_tidy.sh
|
||||
|
||||
# execute "golangci-lint" to check code style
|
||||
# go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
||||
.PHONY: lint
|
||||
lint:
|
||||
golangci-lint run -c .golangci.yml
|
||||
|
||||
10
README.MD
10
README.MD
@ -1,3 +1,4 @@
|
||||
English | [简体中文](README.zh_CN.MD)
|
||||
|
||||
<div align=center>
|
||||
<img src="https://goframe.org/img/logo_full.png" width="300" alt="goframe gf logo"/>
|
||||
@ -23,6 +24,12 @@
|
||||
|
||||
A powerful framework for faster, easier, and more efficient project development.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get -u github.com/gogf/gf/v2
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- Official Site: [https://goframe.org](https://goframe.org)
|
||||
@ -31,13 +38,14 @@ A powerful framework for faster, easier, and more efficient project development.
|
||||
- Mirror Site: [Github Pages](https://pages.goframe.org)
|
||||
- Mirror Site: [Offline Docs](https://github.com/gogf/goframe.org-pdf?tab=readme-ov-file#%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC)
|
||||
- GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
|
||||
- Doc Source: [https://github.com/gogf/gf-site](https://github.com/gogf/gf-site)
|
||||
|
||||
## Contributors
|
||||
|
||||
💖 [Thanks to all the contributors who made GoFrame possible](https://github.com/gogf/gf/graphs/contributors) 💖
|
||||
|
||||
<a href="https://github.com/gogf/gf/graphs/contributors">
|
||||
<img src="https://goframe.org/img/contributors.svg?version=v2.9.5" alt="goframe contributors"/>
|
||||
<img src="https://goframe.org/img/contributors.svg?version=v2.9.7" alt="goframe contributors"/>
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
||||
53
README.zh_CN.MD
Normal file
53
README.zh_CN.MD
Normal file
@ -0,0 +1,53 @@
|
||||
[English](README.MD) | 简体中文
|
||||
|
||||
<div align=center>
|
||||
<img src="https://goframe.org/img/logo_full.png" width="300" alt="goframe gf logo"/>
|
||||
|
||||
[](https://pkg.go.dev/github.com/gogf/gf/v2)
|
||||
[](https://github.com/gogf/gf/actions/workflows/ci-main.yml)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/gogf/gf)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/9233)
|
||||
[](https://goreportcard.com/report/github.com/gogf/gf/v2)
|
||||
[](https://codecov.io/gh/gogf/gf)
|
||||
[](https://github.com/gogf/gf)
|
||||
[](https://github.com/gogf/gf)
|
||||
|
||||
[](https://github.com/gogf/gf/releases)
|
||||
[](https://github.com/gogf/gf/pulls)
|
||||
[](https://github.com/gogf/gf/pulls?q=is%3Apr+is%3Aclosed)
|
||||
[](https://github.com/gogf/gf/issues)
|
||||
[](https://github.com/gogf/gf/issues?q=is%3Aissue+is%3Aclosed)
|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
一个强大的框架,为了更快、更轻松、更高效的项目开发。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
go get -u github.com/gogf/gf/v2
|
||||
```
|
||||
|
||||
## 文档
|
||||
|
||||
- 官方网站: [https://goframe.org](https://goframe.org)
|
||||
- 官方网站(en): [https://goframe.org/en](https://goframe.org/en)
|
||||
- 国内镜像: [https://goframe.org.cn](https://goframe.org.cn)
|
||||
- 镜像网站: [Github Pages](https://pages.goframe.org)
|
||||
- 镜像网站: [离线文档](https://github.com/gogf/goframe.org-pdf?tab=readme-ov-file#%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC)
|
||||
- Go包文档: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
|
||||
- 文档源码: [https://github.com/gogf/gf-site](https://github.com/gogf/gf-site)
|
||||
|
||||
## 贡献者
|
||||
|
||||
💖 [感谢所有使 GoFrame 成为可能的贡献者](https://github.com/gogf/gf/graphs/contributors) 💖
|
||||
|
||||
<a href="https://github.com/gogf/gf/graphs/contributors">
|
||||
<img src="https://goframe.org/img/contributors.svg?version=v2.9.5" alt="goframe contributors"/>
|
||||
</a>
|
||||
|
||||
## 许可证
|
||||
|
||||
`GoFrame` 采用 [MIT License](LICENSE) 许可,100% 免费和开源,永久保持。
|
||||
@ -1,3 +1,5 @@
|
||||
English | [简体中文](README.zh_CN.MD)
|
||||
|
||||
# gf
|
||||
|
||||
`gf` is a powerful CLI tool for building [GoFrame](https://goframe.org) application with convenience.
|
||||
@ -21,18 +23,18 @@ You can also install `gf` tool using pre-built binaries: <https://github.com/gog
|
||||
|
||||
3. Database support
|
||||
|
||||
| DB | builtin support | remarks |
|
||||
|:----------:|:---------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------:|
|
||||
| mysql | yes | - |
|
||||
| mariadb | yes | - |
|
||||
| tidb | yes | - |
|
||||
| mssql | yes | - |
|
||||
| oracle | yes | - |
|
||||
| pgsql | yes | - |
|
||||
| sqlite | yes | - |
|
||||
| sqlitecgo | no | to support sqlite database on 32bit architecture systems, manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
|
||||
| clickhouse | no | manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
|
||||
| dm | no | manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
|
||||
| DB | builtin support | remarks |
|
||||
| :--------: | :-------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| mysql | yes | - |
|
||||
| mariadb | yes | - |
|
||||
| tidb | yes | - |
|
||||
| mssql | yes | - |
|
||||
| oracle | yes | - |
|
||||
| pgsql | yes | - |
|
||||
| sqlite | yes | - |
|
||||
| sqlitecgo | no | to support sqlite database on 32bit architecture systems, manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
|
||||
| clickhouse | yes | - |
|
||||
| dm | no | manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
|
||||
|
||||
## 2) Manually Install
|
||||
|
||||
@ -43,30 +45,31 @@ go install github.com/gogf/gf/cmd/gf/v2@v2.5.5 # certain version(should be >= v2
|
||||
|
||||
## 2. Commands
|
||||
|
||||
```html
|
||||
$ gf
|
||||
```shell
|
||||
$ gf -h
|
||||
USAGE
|
||||
gf COMMAND [OPTION]
|
||||
|
||||
COMMAND
|
||||
up upgrade GoFrame version/tool to latest one in current project
|
||||
env show current Golang environment variables
|
||||
fix auto fixing codes after upgrading to new GoFrame version
|
||||
run running go codes with hot-compiled-like feature
|
||||
gen automatically generate go files for dao/do/entity/pb/pbentity
|
||||
tpl template parsing and building commands
|
||||
init create and initialize an empty GoFrame project
|
||||
pack packing any file/directory to a resource file, or a go file
|
||||
build cross-building go project for lots of platforms
|
||||
docker build docker image for current GoFrame project
|
||||
install install gf binary to system (might need root/admin permission)
|
||||
version show version information of current binary
|
||||
up upgrade GoFrame version/tool to latest one in current project
|
||||
env show current Golang environment variables
|
||||
fix auto fixing codes after upgrading to new GoFrame version
|
||||
run running go codes with hot-compiled-like feature
|
||||
gen automatically generate go files for dao/do/entity/pb/pbentity
|
||||
tpl template parsing and building commands
|
||||
init create and initialize an empty GoFrame project
|
||||
pack packing any file/directory to a resource file, or a go file
|
||||
build cross-building go project for lots of platforms
|
||||
docker build docker image for current GoFrame project
|
||||
install install gf binary to system (might need root/admin permission)
|
||||
version show version information of current binary
|
||||
doc download https://pages.goframe.org/ to run locally
|
||||
|
||||
OPTION
|
||||
-y, --yes all yes for all command without prompt ask
|
||||
-v, --version show version information of current binary
|
||||
-d, --debug show internal detailed debugging information
|
||||
-h, --help more information about this command
|
||||
-y, --yes all yes for all command without prompt ask
|
||||
-v, --version show version information of current binary
|
||||
-d, --debug show internal detailed debugging information
|
||||
-h, --help more information about this command
|
||||
|
||||
ADDITIONAL
|
||||
Use "gf COMMAND -h" for details about a command.
|
||||
|
||||
82
cmd/gf/README.zh_CN.MD
Normal file
82
cmd/gf/README.zh_CN.MD
Normal file
@ -0,0 +1,82 @@
|
||||
[English](README.MD) | 简体中文
|
||||
|
||||
# gf
|
||||
|
||||
`gf` 是一个强大的 CLI 工具,用于便捷地构建 [GoFrame](https://goframe.org) 应用程序。
|
||||
|
||||
## 1. 安装
|
||||
|
||||
## 1) 预编译二进制文件
|
||||
|
||||
您也可以使用预构建的二进制文件安装 `gf` 工具:<https://github.com/gogf/gf/releases>
|
||||
|
||||
1. `Mac` & `Linux`
|
||||
|
||||
```shell
|
||||
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH) && chmod +x gf && ./gf install -y && rm ./gf
|
||||
```
|
||||
|
||||
> 如果您使用 `zsh`,您可能需要通过命令 `alias gf=gf` 重命名别名以解决 `gf` 和 `git fetch` 之间的冲突。
|
||||
|
||||
2. `Windows`
|
||||
手动下载,在命令行中执行,然后按照说明操作。
|
||||
|
||||
3. 数据库支持
|
||||
|
||||
| 数据库 | 内置支持 | 说明 |
|
||||
| :--------: | :-------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| mysql | 是 | - |
|
||||
| mariadb | 是 | - |
|
||||
| tidb | 是 | - |
|
||||
| mssql | 是 | - |
|
||||
| oracle | 是 | - |
|
||||
| pgsql | 是 | - |
|
||||
| sqlite | 是 | - |
|
||||
| sqlitecgo | 否 | 要在 32 位架构系统上支持 sqlite 数据库,请手动向[源代码](./internal/cmd/cmd_gen_dao.go)添加包导入并进行构建。 |
|
||||
| clickhouse | 是 | - |
|
||||
| dm | 否 | 手动向[源代码](./internal/cmd/cmd_gen_dao.go)添加包导入并进行构建。 |
|
||||
|
||||
## 2) 手动安装
|
||||
|
||||
```shell
|
||||
go install github.com/gogf/gf/cmd/gf/v2@latest # 最新版本
|
||||
go install github.com/gogf/gf/cmd/gf/v2@v2.5.5 # 特定版本(应该 >= v2.5.5)
|
||||
```
|
||||
|
||||
## 2. 命令
|
||||
|
||||
```shell
|
||||
$ gf -h
|
||||
用法
|
||||
gf 命令 [选项]
|
||||
|
||||
命令
|
||||
up 升级项目中的 GoFrame 版本/工具到最新版本
|
||||
env 显示当前 Golang 环境变量
|
||||
fix 升级到新 GoFrame 版本后自动修复代码
|
||||
run 运行 go 代码,具有热编译功能
|
||||
gen 自动生成 dao/do/entity/pb/pbentity 的 go 文件
|
||||
tpl 模板解析和构建命令
|
||||
init 创建并初始化一个空的 GoFrame 项目
|
||||
pack 将任何文件/目录打包到资源文件或 go 文件
|
||||
build 为多个平台交叉编译 go 项目
|
||||
docker 为当前 GoFrame 项目构建 docker 镜像
|
||||
install 将 gf 二进制文件安装到系统(可能需要 root/admin 权限)
|
||||
version 显示当前二进制文件的版本信息
|
||||
doc 下载 https://pages.goframe.org/ 本地运行
|
||||
|
||||
选项
|
||||
-y, --yes 对所有命令都使用 yes,不再提示
|
||||
-v, --version 显示当前二进制文件的版本信息
|
||||
-d, --debug 显示内部详细的调试信息
|
||||
-h, --help 显示此命令的更多信息
|
||||
|
||||
附加信息
|
||||
使用 "gf 命令 -h" 获取有关命令的详细信息。
|
||||
```
|
||||
|
||||
## 3. 常见问题
|
||||
|
||||
### 1). 命令 `gf run` 返回 `pipe: too many open files`
|
||||
|
||||
请使用 `ulimit -n 65535` 扩大系统配置以增加当前终端 shell 会话的最大打开文件数,然后再运行 `gf run`。
|
||||
@ -3,13 +3,13 @@ module github.com/gogf/gf/cmd/gf/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.5
|
||||
github.com/gogf/gf/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.7
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.7
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.7
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.7
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.7
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.7
|
||||
github.com/gogf/gf/v2 v2.9.7
|
||||
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f
|
||||
github.com/olekukonko/tablewriter v1.1.0
|
||||
github.com/schollz/progressbar/v3 v3.15.0
|
||||
@ -23,7 +23,7 @@ require (
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.15 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
|
||||
@ -27,8 +27,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
@ -46,20 +46,6 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.5 h1:WTbuQvbtOSddi2GtiobM/FCRUr5god7yzlEQP7WGOM8=
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.5/go.mod h1:/lnvHd9+8VmjDLdgIczzRTJA1tZYY5AA8bdcaexi/ao=
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.5 h1:hgkgzbi6j8tDf+UpjG01fLqnAchD3913RZ37ruY9TqE=
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.5/go.mod h1:Sy0DQNJ/xEL4snJyWqcflmYGr2jE8Tn9mynxqbe2Dds=
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5 h1:0+ZBYhi4sqwxXwL+hIBpp06a7G4m5nmjskQ3NNb8qYc=
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5/go.mod h1:vyB7J/uJcLCrHD5lfFBzxhEEMkePIRzfhd33EcsuLa0=
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.5 h1:eCPD7oteF/gurCU5ZfAhFBU7E1vI85cnoHKRdckn9Vc=
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.5/go.mod h1:e5sxdxw3OrAFB+4VdYt3Wbm0G7LP5wofNRKj3JHIots=
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.5 h1:FCgvTLBuhPX7HE6YyR/dbNASoiKv72jpVS5SqoYYJTw=
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.5/go.mod h1:/Lz1qzhYN3ogt5aMFoIfjp82SbeuzjpXCqQhFu4Z3MI=
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.5 h1:MooWqn5qLMfB105PlBvd2Z7J3KdVBsjYxULtb42u308=
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.5/go.mod h1:pr68Q85xhRnJ5PhyGte/LiFJ/m+998RPjW+Jn+w+xYo=
|
||||
github.com/gogf/gf/v2 v2.9.5 h1:1scfOdHbMP854oQaiLejl+eL+c4xfuvtWmmZiDJxbKs=
|
||||
github.com/gogf/gf/v2 v2.9.5/go.mod h1:VUb5eyJKpvW77O/dXsbbLNO/Kjrg0UycIiq0lRiBjjo=
|
||||
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f h1:7xfXR/BhG3JDqO1s45n65Oyx9t4E/UqDOXep6jXdLCM=
|
||||
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f/go.mod h1:HnYoio6S7VaFJdryKcD/r9HgX+4QzYfr00XiXUo/xz0=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
|
||||
@ -14,5 +14,9 @@ replace (
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 => ../../contrib/drivers/oracle
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 => ../../contrib/drivers/pgsql
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 => ../../contrib/drivers/sqlite
|
||||
github.com/gogf/gf/contrib/drivers/mariadb/v2 => ../../contrib/drivers/mariadb
|
||||
github.com/gogf/gf/contrib/drivers/tidb/v2 => ../../contrib/drivers/tidb
|
||||
github.com/gogf/gf/contrib/drivers/oceanbase/v2 => ../../contrib/drivers/oceanbase
|
||||
github.com/gogf/gf/contrib/drivers/gaussdb/v2 => ../../contrib/drivers/gaussdb
|
||||
github.com/gogf/gf/v2 => ../../
|
||||
)
|
||||
|
||||
@ -7,9 +7,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@ -20,6 +22,7 @@ import (
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/geninit"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/allyes"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
|
||||
@ -44,6 +47,12 @@ const (
|
||||
gf init my-project
|
||||
gf init my-mono-repo -m
|
||||
gf init my-mono-repo -a
|
||||
gf init my-project -u
|
||||
gf init my-project -g "github.com/myorg/myproject"
|
||||
gf init -r github.com/gogf/template-single my-project
|
||||
gf init -r github.com/gogf/template-single my-project -s
|
||||
gf init -r github.com/gogf/examples/httpserver/jwt my-jwt
|
||||
gf init -i
|
||||
`
|
||||
cInitNameBrief = `
|
||||
name for the project. It will create a folder with NAME in current directory.
|
||||
@ -55,6 +64,16 @@ The NAME will also be the module name for the project.
|
||||
cInitGitignore = ".gitignore"
|
||||
)
|
||||
|
||||
// defaultTemplates is the list of predefined templates for interactive selection
|
||||
var defaultTemplates = []struct {
|
||||
Name string
|
||||
Repo string
|
||||
Desc string
|
||||
}{
|
||||
{"template-single", "github.com/gogf/template-single", "Single project template"},
|
||||
{"template-mono", "github.com/gogf/template-mono", "Mono-repo project template"},
|
||||
}
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cInitBrief`: cInitBrief,
|
||||
@ -64,17 +83,86 @@ func init() {
|
||||
}
|
||||
|
||||
type cInitInput struct {
|
||||
g.Meta `name:"init"`
|
||||
Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"`
|
||||
Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
|
||||
MonoApp bool `name:"monoApp" short:"a" brief:"initialize a mono-repo-app instead a single-repo" orphan:"true"`
|
||||
Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"`
|
||||
Module string `name:"module" short:"g" brief:"custom go module"`
|
||||
g.Meta `name:"init"`
|
||||
Name string `name:"NAME" arg:"true" brief:"{cInitNameBrief}"`
|
||||
Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
|
||||
MonoApp bool `name:"monoApp" short:"a" brief:"initialize a mono-repo-app instead a single-repo" orphan:"true"`
|
||||
Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"`
|
||||
Module string `name:"module" short:"g" brief:"custom go module"`
|
||||
Repo string `name:"repo" short:"r" brief:"remote repository URL for template download"`
|
||||
SelectVer bool `name:"select" short:"s" brief:"enable interactive version selection for remote template" orphan:"true"`
|
||||
Interactive bool `name:"interactive" short:"i" brief:"enable interactive mode to select template" orphan:"true"`
|
||||
}
|
||||
|
||||
type cInitOutput struct{}
|
||||
|
||||
func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
// Check if using remote template mode
|
||||
if in.Repo != "" || in.Interactive {
|
||||
return c.initFromRemote(ctx, in)
|
||||
}
|
||||
|
||||
// If no name provided and no remote mode, enter interactive mode
|
||||
if in.Name == "" {
|
||||
return c.initInteractive(ctx, in)
|
||||
}
|
||||
|
||||
// Default: use built-in template
|
||||
return c.initFromBuiltin(ctx, in)
|
||||
}
|
||||
|
||||
// initFromRemote initializes project from remote repository
|
||||
func (c cInit) initFromRemote(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
repo := in.Repo
|
||||
name := in.Name
|
||||
|
||||
// If interactive mode and no repo specified, let user select
|
||||
if in.Interactive && repo == "" {
|
||||
var modPath string
|
||||
var upgradeDeps bool
|
||||
repo, name, modPath, upgradeDeps, err = interactiveSelectTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if modPath != "" {
|
||||
in.Module = modPath
|
||||
}
|
||||
if upgradeDeps {
|
||||
in.Update = true
|
||||
}
|
||||
}
|
||||
|
||||
if repo == "" {
|
||||
return nil, fmt.Errorf("repository URL is required for remote template mode")
|
||||
}
|
||||
|
||||
// Default name to repo basename if empty
|
||||
if name == "" {
|
||||
name = gfile.Basename(repo)
|
||||
mlog.Printf("Using repository basename as project name: %s", name)
|
||||
}
|
||||
|
||||
mlog.Print("initializing from remote template...")
|
||||
|
||||
opts := &geninit.ProcessOptions{
|
||||
SelectVersion: in.SelectVer,
|
||||
ModulePath: in.Module,
|
||||
UpgradeDeps: in.Update,
|
||||
}
|
||||
|
||||
if err = geninit.Process(ctx, repo, name, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mlog.Print("initialization done!")
|
||||
if name != "" && name != "." {
|
||||
mlog.Printf(`you can now run "cd %s && gf run main.go" to start your journey, enjoy!`, name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// initFromBuiltin initializes project from built-in template
|
||||
func (c cInit) initFromBuiltin(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
var overwrote = false
|
||||
if !gfile.IsEmpty(in.Name) && !allyes.Check() {
|
||||
s := gcmd.Scanf(`the folder "%s" is not empty, files might be overwrote, continue? [y/n]: `, in.Name)
|
||||
@ -180,3 +268,170 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// initInteractive enters interactive mode when no arguments provided
|
||||
func (c cInit) initInteractive(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
// Ask user which mode to use
|
||||
fmt.Println("\nPlease select initialization mode:")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
fmt.Println(" [1] Built-in template (default)")
|
||||
fmt.Println(" [2] Remote template")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
|
||||
fmt.Print("Select mode [1-2] (default: 1): ")
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
mlog.Fatalf("failed to read input: %v", err)
|
||||
return
|
||||
}
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
if input == "2" {
|
||||
in.Interactive = true
|
||||
return c.initFromRemote(ctx, in)
|
||||
}
|
||||
|
||||
// Built-in template mode
|
||||
fmt.Println("\nPlease select project type:")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
fmt.Println(" [1] Single project (default)")
|
||||
fmt.Println(" [2] Mono-repo project")
|
||||
fmt.Println(" [3] Mono-repo app")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
|
||||
fmt.Print("Select type [1-3] (default: 1): ")
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
mlog.Fatalf("failed to read input: %v", err)
|
||||
return
|
||||
}
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
switch input {
|
||||
case "2":
|
||||
in.Mono = true
|
||||
case "3":
|
||||
in.MonoApp = true
|
||||
}
|
||||
|
||||
// Get project name
|
||||
for {
|
||||
fmt.Print("Enter project name: ")
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
mlog.Fatalf("failed to read input: %v", err)
|
||||
return
|
||||
}
|
||||
in.Name = strings.TrimSpace(input)
|
||||
if in.Name != "" {
|
||||
break
|
||||
}
|
||||
fmt.Println("Project name cannot be empty")
|
||||
}
|
||||
|
||||
// Get module path (optional)
|
||||
fmt.Printf("Enter Go module path (leave empty to use \"%s\"): ", in.Name)
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
mlog.Fatalf("failed to read input: %v", err)
|
||||
return
|
||||
}
|
||||
in.Module = strings.TrimSpace(input)
|
||||
|
||||
// Ask about update
|
||||
fmt.Print("Update to latest GoFrame version? [y/N]: ")
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
mlog.Fatalf("failed to read input: %v", err)
|
||||
return
|
||||
}
|
||||
input = strings.TrimSpace(strings.ToLower(input))
|
||||
in.Update = input == "y" || input == "yes"
|
||||
|
||||
fmt.Println()
|
||||
return c.initFromBuiltin(ctx, in)
|
||||
}
|
||||
|
||||
// interactiveSelectTemplate prompts user to select a template interactively
|
||||
func interactiveSelectTemplate() (repo, name, modPath string, upgradeDeps bool, err error) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
// 1. Select template
|
||||
fmt.Println("\nPlease select a project template:")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
for i, t := range defaultTemplates {
|
||||
fmt.Printf(" [%d] %s - %s\n", i+1, t.Name, t.Desc)
|
||||
}
|
||||
fmt.Printf(" [%d] Custom repository URL\n", len(defaultTemplates)+1)
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
|
||||
for {
|
||||
fmt.Printf("Select template [1-%d]: ", len(defaultTemplates)+1)
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to read template selection: %w", err)
|
||||
}
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
idx, e := strconv.Atoi(input)
|
||||
if e != nil || idx < 1 || idx > len(defaultTemplates)+1 {
|
||||
fmt.Printf("Invalid selection, please enter a number between 1-%d\n", len(defaultTemplates)+1)
|
||||
continue
|
||||
}
|
||||
|
||||
if idx <= len(defaultTemplates) {
|
||||
repo = defaultTemplates[idx-1].Repo
|
||||
fmt.Printf("Selected: %s\n\n", repo)
|
||||
} else {
|
||||
// Custom URL
|
||||
fmt.Print("Enter repository URL: ")
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to read repository URL: %w", err)
|
||||
}
|
||||
repo = strings.TrimSpace(input)
|
||||
if repo == "" {
|
||||
fmt.Println("Repository URL cannot be empty")
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// 2. Enter project name
|
||||
for {
|
||||
fmt.Print("Enter project name: ")
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to read project name: %w", err)
|
||||
}
|
||||
name = strings.TrimSpace(input)
|
||||
if name == "" {
|
||||
fmt.Println("Project name cannot be empty")
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// 3. Enter module path (optional)
|
||||
fmt.Printf("Enter Go module path (leave empty to use \"%s\"): ", name)
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to read module path: %w", err)
|
||||
}
|
||||
modPath = strings.TrimSpace(input)
|
||||
|
||||
// 4. Ask about upgrade
|
||||
fmt.Print("Upgrade dependencies to latest (go get -u)? [y/N]: ")
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to read upgrade confirmation: %w", err)
|
||||
}
|
||||
input = strings.TrimSpace(strings.ToLower(input))
|
||||
upgradeDeps = input == "y" || input == "yes"
|
||||
|
||||
fmt.Println()
|
||||
return repo, name, modPath, upgradeDeps, nil
|
||||
}
|
||||
|
||||
@ -33,12 +33,18 @@ type cRun struct {
|
||||
g.Meta `name:"run" usage:"{cRunUsage}" brief:"{cRunBrief}" eg:"{cRunEg}" dc:"{cRunDc}"`
|
||||
}
|
||||
|
||||
type watchPath struct {
|
||||
Path string
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
type cRunApp struct {
|
||||
File string // Go run file name.
|
||||
Path string // Directory storing built binary.
|
||||
Options string // Extra "go run" options.
|
||||
Args string // Custom arguments.
|
||||
WatchPaths []string // Watch paths for live reload.
|
||||
File string // Go run file name.
|
||||
Path string // Directory storing built binary.
|
||||
Options string // Extra "go run" options.
|
||||
Args string // Custom arguments.
|
||||
WatchPaths []string // Watch paths for live reload.
|
||||
IgnorePatterns []string // Custom ignore patterns.
|
||||
}
|
||||
|
||||
const (
|
||||
@ -48,43 +54,47 @@ const (
|
||||
gf run main.go
|
||||
gf run main.go --args "server -p 8080"
|
||||
gf run main.go -mod=vendor
|
||||
gf run main.go -w "manifest/config/*.yaml"
|
||||
gf run main.go -w internal,api
|
||||
gf run main.go -i ".git,node_modules"
|
||||
`
|
||||
cRunDc = `
|
||||
The "run" command is used for running go codes with hot-compiled-like feature,
|
||||
which compiles and runs the go codes asynchronously when codes change.
|
||||
`
|
||||
cRunFileBrief = `building file path.`
|
||||
cRunPathBrief = `output directory path for built binary file. it's "./" in default`
|
||||
cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined`
|
||||
cRunArgsBrief = `custom arguments for your process`
|
||||
cRunWatchPathsBrief = `watch additional paths for live reload, separated by ",". i.e. "manifest/config/*.yaml"`
|
||||
cRunFileBrief = `building file path.`
|
||||
cRunPathBrief = `output directory path for built binary file. it's "./" in default`
|
||||
cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined`
|
||||
cRunArgsBrief = `custom arguments for your process`
|
||||
cRunWatchPathsBrief = `watch additional paths for live reload, separated by ",". i.e. "internal,api"`
|
||||
cRunIgnorePatternBrief = `custom ignore patterns for watch, separated by ",". i.e. ".git,node_modules". default patterns: node_modules, vendor, .*, _*. Glob syntax: "*" matches any chars, "?" matches single char, "[abc]" matches char class. Note: patterns match directory names only, not paths`
|
||||
)
|
||||
|
||||
var process *gproc.Process
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cRunUsage`: cRunUsage,
|
||||
`cRunBrief`: cRunBrief,
|
||||
`cRunEg`: cRunEg,
|
||||
`cRunDc`: cRunDc,
|
||||
`cRunFileBrief`: cRunFileBrief,
|
||||
`cRunPathBrief`: cRunPathBrief,
|
||||
`cRunExtraBrief`: cRunExtraBrief,
|
||||
`cRunArgsBrief`: cRunArgsBrief,
|
||||
`cRunWatchPathsBrief`: cRunWatchPathsBrief,
|
||||
`cRunUsage`: cRunUsage,
|
||||
`cRunBrief`: cRunBrief,
|
||||
`cRunEg`: cRunEg,
|
||||
`cRunDc`: cRunDc,
|
||||
`cRunFileBrief`: cRunFileBrief,
|
||||
`cRunPathBrief`: cRunPathBrief,
|
||||
`cRunExtraBrief`: cRunExtraBrief,
|
||||
`cRunArgsBrief`: cRunArgsBrief,
|
||||
`cRunWatchPathsBrief`: cRunWatchPathsBrief,
|
||||
`cRunIgnorePatternBrief`: cRunIgnorePatternBrief,
|
||||
})
|
||||
}
|
||||
|
||||
type (
|
||||
cRunInput struct {
|
||||
g.Meta `name:"run" config:"gfcli.run"`
|
||||
File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"`
|
||||
Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"`
|
||||
Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"`
|
||||
Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"`
|
||||
WatchPaths []string `name:"watchPaths" short:"w" brief:"{cRunWatchPathsBrief}"`
|
||||
g.Meta `name:"run" config:"gfcli.run"`
|
||||
File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"`
|
||||
Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"`
|
||||
Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"`
|
||||
Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"`
|
||||
WatchPaths []string `name:"watchPaths" short:"w" brief:"{cRunWatchPathsBrief}"`
|
||||
IgnorePatterns []string `name:"ignorePatterns" short:"i" brief:"{cRunIgnorePatternBrief}"`
|
||||
}
|
||||
cRunOutput struct{}
|
||||
)
|
||||
@ -101,17 +111,25 @@ func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err err
|
||||
mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
|
||||
}
|
||||
|
||||
if len(in.WatchPaths) == 1 {
|
||||
in.WatchPaths = strings.Split(in.WatchPaths[0], ",")
|
||||
// Parse comma-separated values in WatchPaths
|
||||
if len(in.WatchPaths) > 0 {
|
||||
in.WatchPaths = parseCommaSeparatedArgs(in.WatchPaths)
|
||||
mlog.Printf("watchPaths: %v", in.WatchPaths)
|
||||
}
|
||||
|
||||
// Parse comma-separated values in IgnorePatterns
|
||||
if len(in.IgnorePatterns) > 0 {
|
||||
in.IgnorePatterns = parseCommaSeparatedArgs(in.IgnorePatterns)
|
||||
mlog.Printf("ignorePatterns: %v", in.IgnorePatterns)
|
||||
}
|
||||
|
||||
app := &cRunApp{
|
||||
File: in.File,
|
||||
Path: filepath.FromSlash(in.Path),
|
||||
Options: in.Extra,
|
||||
Args: in.Args,
|
||||
WatchPaths: in.WatchPaths,
|
||||
File: in.File,
|
||||
Path: filepath.FromSlash(in.Path),
|
||||
Options: in.Extra,
|
||||
Args: in.Args,
|
||||
WatchPaths: in.WatchPaths,
|
||||
IgnorePatterns: in.IgnorePatterns,
|
||||
}
|
||||
dirty := gtype.NewBool()
|
||||
|
||||
@ -121,6 +139,7 @@ func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err err
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the file extension is 'go'.
|
||||
if gfile.ExtName(event.Path) != "go" {
|
||||
return
|
||||
}
|
||||
@ -138,15 +157,11 @@ func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err err
|
||||
})
|
||||
}
|
||||
|
||||
if len(app.WatchPaths) > 0 {
|
||||
for _, path := range app.WatchPaths {
|
||||
_, err = gfsnotify.Add(gfile.RealPath(path), callbackFunc)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err = gfsnotify.Add(gfile.RealPath("."), callbackFunc)
|
||||
// Get directories to watch (recursive or non-recursive monitoring).
|
||||
watchPaths := app.getWatchPaths()
|
||||
for _, wp := range watchPaths {
|
||||
option := gfsnotify.WatchOption{NoRecursive: !wp.Recursive}
|
||||
_, err = gfsnotify.Add(wp.Path, callbackFunc, option)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
@ -249,35 +264,181 @@ func (app *cRunApp) End(ctx context.Context, sig os.Signal, outputPath string) {
|
||||
}
|
||||
|
||||
func (app *cRunApp) genOutputPath() (outputPath string) {
|
||||
var renamePath string
|
||||
outputPath = gfile.Join(app.Path, gfile.Name(app.File))
|
||||
if runtime.GOOS == "windows" {
|
||||
outputPath += ".exe"
|
||||
if gfile.Exists(outputPath) {
|
||||
renamePath = outputPath + "~"
|
||||
renamePath := outputPath + "~"
|
||||
if err := gfile.Rename(outputPath, renamePath); err != nil {
|
||||
mlog.Print(err)
|
||||
}
|
||||
// Clean up the renamed old binary file
|
||||
defer func() {
|
||||
if gfile.Exists(renamePath) {
|
||||
_ = gfile.Remove(renamePath)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
return filepath.FromSlash(outputPath)
|
||||
}
|
||||
|
||||
func matchWatchPaths(watchPaths []string, eventPath string) bool {
|
||||
for _, path := range watchPaths {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
mlog.Printf("match watchPath '%s' error: %s", path, err.Error())
|
||||
// getWatchPaths uses DFS to find the minimal set of directories to watch.
|
||||
// Rule: if a directory and all its descendants have no ignored subdirectories, watch it;
|
||||
// otherwise, recurse into valid children and watch the current directory non-recursively.
|
||||
func (app *cRunApp) getWatchPaths() []watchPath {
|
||||
roots := []string{"."}
|
||||
if len(app.WatchPaths) > 0 {
|
||||
roots = app.WatchPaths
|
||||
}
|
||||
|
||||
// Use custom ignore patterns if provided, otherwise use default.
|
||||
ignorePatterns := defaultIgnorePatterns
|
||||
if len(app.IgnorePatterns) > 0 {
|
||||
ignorePatterns = app.IgnorePatterns
|
||||
}
|
||||
|
||||
var watchPaths []watchPath
|
||||
|
||||
for _, root := range roots {
|
||||
absRoot := gfile.RealPath(root)
|
||||
if absRoot == "" {
|
||||
mlog.Printf("watch path '%s' not found, skipping", root)
|
||||
continue
|
||||
}
|
||||
matched, err := filepath.Match(absPath, eventPath)
|
||||
if err != nil {
|
||||
mlog.Printf("match watchPath '%s' error: %s", path, err.Error())
|
||||
if isIgnoredDirName(absRoot, ignorePatterns) {
|
||||
continue
|
||||
}
|
||||
if matched {
|
||||
app.collectWatchPaths(absRoot, ignorePatterns, &watchPaths)
|
||||
}
|
||||
|
||||
if len(watchPaths) == 0 {
|
||||
mlog.Printf("no directories to watch, using current directory")
|
||||
if absCur := gfile.RealPath("."); absCur != "" {
|
||||
return []watchPath{{Path: absCur, Recursive: true}}
|
||||
}
|
||||
return []watchPath{{Path: ".", Recursive: true}}
|
||||
}
|
||||
|
||||
mlog.Printf("watching %d paths", len(watchPaths))
|
||||
for _, wp := range watchPaths {
|
||||
recursiveStr := "recursive"
|
||||
if !wp.Recursive {
|
||||
recursiveStr = "non-recursive"
|
||||
}
|
||||
mlog.Debugf(" - %s (%s)", wp.Path, recursiveStr)
|
||||
}
|
||||
return watchPaths
|
||||
}
|
||||
|
||||
// collectWatchPaths performs a DFS traversal to collect the minimal set of directories to watch.
|
||||
// Returns true if the directory or any of its descendants contains ignored directories.
|
||||
// Rule: if a directory has no ignored descendants at any depth, watch it recursively;
|
||||
// otherwise, watch it non-recursively and recurse into valid children.
|
||||
func (app *cRunApp) collectWatchPaths(dir string, ignorePatterns []string, watchPaths *[]watchPath) bool {
|
||||
entries, err := gfile.ScanDir(dir, "*", false)
|
||||
if err != nil {
|
||||
mlog.Printf("scan directory '%s' error: %s", dir, err.Error())
|
||||
// If we can't scan the directory, add it to watch list as fallback
|
||||
*watchPaths = append(*watchPaths, watchPath{Path: dir, Recursive: true})
|
||||
return false
|
||||
}
|
||||
|
||||
// First pass: identify valid subdirectories and check for directly ignored children
|
||||
var validSubDirs []string
|
||||
hasIgnoredChild := false
|
||||
for _, entry := range entries {
|
||||
if !gfile.IsDir(entry) {
|
||||
continue
|
||||
}
|
||||
if isIgnoredDirName(entry, ignorePatterns) {
|
||||
hasIgnoredChild = true
|
||||
} else {
|
||||
validSubDirs = append(validSubDirs, entry)
|
||||
}
|
||||
}
|
||||
|
||||
// If already has ignored child, we know this dir needs non-recursive watch
|
||||
if hasIgnoredChild {
|
||||
*watchPaths = append(*watchPaths, watchPath{Path: dir, Recursive: false})
|
||||
for _, subDir := range validSubDirs {
|
||||
app.collectWatchPaths(subDir, ignorePatterns, watchPaths)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// No ignored children, but need to check descendants recursively
|
||||
// Collect results from all subdirectories first
|
||||
subResults := make([]bool, len(validSubDirs))
|
||||
subWatchPaths := make([][]watchPath, len(validSubDirs))
|
||||
hasIgnoredDescendant := false
|
||||
|
||||
for i, subDir := range validSubDirs {
|
||||
var subPaths []watchPath
|
||||
subResults[i] = app.collectWatchPaths(subDir, ignorePatterns, &subPaths)
|
||||
subWatchPaths[i] = subPaths
|
||||
if subResults[i] {
|
||||
hasIgnoredDescendant = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasIgnoredDescendant {
|
||||
// No ignored descendants at any depth, watch this directory recursively
|
||||
*watchPaths = append(*watchPaths, watchPath{Path: dir, Recursive: true})
|
||||
return false
|
||||
}
|
||||
|
||||
// Has ignored descendants, watch current directory non-recursively
|
||||
// and add all collected subdirectory watch paths
|
||||
*watchPaths = append(*watchPaths, watchPath{Path: dir, Recursive: false})
|
||||
for _, subPaths := range subWatchPaths {
|
||||
*watchPaths = append(*watchPaths, subPaths...)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// defaultIgnorePatterns contains glob patterns for directory names that should be ignored when watching.
|
||||
// These directories typically contain third-party code or non-source files.
|
||||
// Supported glob syntax (filepath.Match):
|
||||
// - "*" matches any sequence of non-separator characters
|
||||
// - "?" matches any single non-separator character
|
||||
// - "[abc]" matches any character in the bracket
|
||||
// - "[a-z]" matches any character in the range
|
||||
// - "[^abc]" or "[!abc]" matches any character not in the bracket
|
||||
//
|
||||
// Note: patterns match directory base names only, not full paths (no "/" or path separators allowed).
|
||||
var defaultIgnorePatterns = []string{
|
||||
"node_modules",
|
||||
"vendor",
|
||||
".*", // All hidden directories (covers .git, .svn, .hg, .idea, .vscode, etc.)
|
||||
"_*", // Directories starting with underscore
|
||||
}
|
||||
|
||||
// isIgnoredDirName checks if a directory name matches any ignored pattern.
|
||||
// It accepts either a full path or just the directory name, but only matches against the base name.
|
||||
// Note: patterns should not contain "/" as they only match directory names, not paths.
|
||||
func isIgnoredDirName(name string, ignorePatterns []string) bool {
|
||||
baseName := gfile.Basename(name)
|
||||
for _, pattern := range ignorePatterns {
|
||||
if matched, _ := filepath.Match(pattern, baseName); matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parseCommaSeparatedArgs parses command line arguments that may contain comma-separated values.
|
||||
// It handles both single argument with commas (e.g., "a,b,c") and multiple arguments.
|
||||
func parseCommaSeparatedArgs(args []string) []string {
|
||||
var result []string
|
||||
for _, arg := range args {
|
||||
parts := strings.Split(arg, ",")
|
||||
for _, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed != "" {
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
336
cmd/gf/internal/cmd/cmd_z_unit_run_test.go
Normal file
336
cmd/gf/internal/cmd/cmd_z_unit_run_test.go
Normal file
@ -0,0 +1,336 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func Test_cRunApp_getWatchPaths_Basic(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{"."},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
for _, v := range watchPaths {
|
||||
t.Log(v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_EmptyWatchPaths(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should default to current directory "."
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_CustomIgnorePattern(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{"testdata"},
|
||||
IgnorePatterns: []string{"2572"},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Ensure the "2572" directory is not watched directly.
|
||||
for _, wp := range watchPaths {
|
||||
t.Log("watch path:", wp)
|
||||
t.Assert(strings.HasSuffix(wp.Path, "2572"), false)
|
||||
}
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_WithIgnoredDirectories(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a temporary directory structure for testing
|
||||
tempDir := gfile.Temp("gf_run_test")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
// Create directory structure:
|
||||
// tempDir/
|
||||
// ├── src/
|
||||
// │ ├── api/
|
||||
// │ └── internal/
|
||||
// ├── vendor/ <-- ignored
|
||||
// └── node_modules/ <-- ignored
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "api"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "internal"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "vendor"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "node_modules"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should watch tempDir non-recursively (to catch top-level files) and src recursively
|
||||
t.Assert(len(watchPaths), 2)
|
||||
// First path is tempDir (non-recursive)
|
||||
t.Assert(watchPaths[0].Path, tempDir)
|
||||
t.Assert(watchPaths[0].Recursive, false)
|
||||
// Second path is src (recursive, since it has no ignored descendants)
|
||||
t.Assert(watchPaths[1].Path, filepath.Join(tempDir, "src"))
|
||||
t.Assert(watchPaths[1].Recursive, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_NoIgnoredDirectories(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a temporary directory structure without ignored directories
|
||||
tempDir := gfile.Temp("gf_run_test_no_ignore")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
// Create directory structure without ignored patterns:
|
||||
// tempDir/
|
||||
// ├── src/
|
||||
// │ ├── api/
|
||||
// │ └── internal/
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "api"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "internal"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should watch the root directory recursively since no ignored directories exist
|
||||
t.Assert(len(watchPaths), 1)
|
||||
t.Assert(watchPaths[0].Path, tempDir)
|
||||
t.Assert(watchPaths[0].Recursive, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_CustomIgnorePatterns(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a temporary directory structure
|
||||
tempDir := gfile.Temp("gf_run_test_custom_ignore")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
// Create directory structure:
|
||||
// tempDir/
|
||||
// ├── src/
|
||||
// │ ├── api/
|
||||
// │ └── internal/
|
||||
// ├── build/ <-- ignored
|
||||
// └── dist/ <-- ignored
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "api"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "internal"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "build"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "dist"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
IgnorePatterns: []string{"build", "dist"},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should watch tempDir non-recursively and src recursively
|
||||
t.Assert(len(watchPaths), 2)
|
||||
t.Assert(watchPaths[0].Path, tempDir)
|
||||
t.Assert(watchPaths[0].Recursive, false)
|
||||
t.Assert(watchPaths[1].Path, filepath.Join(tempDir, "src"))
|
||||
t.Assert(watchPaths[1].Recursive, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_DeepNestedStructure(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a deep nested directory structure
|
||||
tempDir := gfile.Temp("gf_run_test_deep")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
// Create deep directory structure:
|
||||
// tempDir/
|
||||
// ├── a/
|
||||
// │ ├── b/
|
||||
// │ │ └── c/
|
||||
// │ └── vendor/ <-- ignored
|
||||
// └── d/
|
||||
gfile.Mkdir(filepath.Join(tempDir, "a", "b", "c"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "a", "vendor"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "d"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should watch individual valid directories due to ignored vendor directory
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
|
||||
// Verify that vendor directory is not in watch list
|
||||
for _, wp := range watchPaths {
|
||||
t.Assert(strings.Contains(wp.Path, "vendor"), false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_MultipleRoots(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create multiple temporary directories
|
||||
tempDir1 := gfile.Temp("gf_run_test_multi1")
|
||||
tempDir2 := gfile.Temp("gf_run_test_multi2")
|
||||
defer gfile.Remove(tempDir1)
|
||||
defer gfile.Remove(tempDir2)
|
||||
|
||||
gfile.Mkdir(filepath.Join(tempDir1, "src"))
|
||||
gfile.Mkdir(filepath.Join(tempDir2, "api"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir1, tempDir2},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should watch both root directories recursively
|
||||
t.Assert(len(watchPaths), 2)
|
||||
|
||||
// Both directories should be in the watch list
|
||||
foundDir1, foundDir2 := false, false
|
||||
for _, wp := range watchPaths {
|
||||
if wp.Path == tempDir1 {
|
||||
foundDir1 = true
|
||||
t.Assert(wp.Recursive, true)
|
||||
}
|
||||
if wp.Path == tempDir2 {
|
||||
foundDir2 = true
|
||||
t.Assert(wp.Recursive, true)
|
||||
}
|
||||
}
|
||||
t.Assert(foundDir1, true)
|
||||
t.Assert(foundDir2, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_NonExistentDirectory(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{"/non/existent/path"},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should fall back to current directory when no valid paths found
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
|
||||
// Should contain current directory
|
||||
currentDir, _ := os.Getwd()
|
||||
foundCurrentDir := false
|
||||
for _, wp := range watchPaths {
|
||||
if wp.Path == currentDir {
|
||||
foundCurrentDir = true
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Assert(foundCurrentDir, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_isIgnoredDirName(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test default ignore patterns
|
||||
t.Assert(isIgnoredDirName("node_modules", defaultIgnorePatterns), true)
|
||||
t.Assert(isIgnoredDirName("vendor", defaultIgnorePatterns), true)
|
||||
t.Assert(isIgnoredDirName(".git", defaultIgnorePatterns), true)
|
||||
t.Assert(isIgnoredDirName("_private", defaultIgnorePatterns), true)
|
||||
t.Assert(isIgnoredDirName("src", defaultIgnorePatterns), false)
|
||||
t.Assert(isIgnoredDirName("api", defaultIgnorePatterns), false)
|
||||
|
||||
// Test custom ignore patterns
|
||||
customPatterns := []string{"build", "dist", "*.tmp"}
|
||||
t.Assert(isIgnoredDirName("build", customPatterns), true)
|
||||
t.Assert(isIgnoredDirName("dist", customPatterns), true)
|
||||
t.Assert(isIgnoredDirName("test.tmp", customPatterns), true)
|
||||
t.Assert(isIgnoredDirName("src", customPatterns), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_DeeplyNestedIgnore(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a temporary directory structure with deeply nested ignored directory
|
||||
tempDir := gfile.Temp("gf_run_test_deeply_nested")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
// Create directory structure:
|
||||
// tempDir/
|
||||
// ├── a/
|
||||
// │ ├── b/
|
||||
// │ │ ├── c/
|
||||
// │ │ │ └── vendor/ <-- deeply nested ignored (4 levels)
|
||||
// │ │ └── d/
|
||||
// │ └── e/
|
||||
// └── f/
|
||||
gfile.Mkdir(filepath.Join(tempDir, "a", "b", "c", "vendor"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "a", "b", "d"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "a", "e"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "f"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Expected watch paths:
|
||||
// 1. tempDir (non-recursive) - has ignored descendant
|
||||
// 2. a (non-recursive) - has ignored descendant in b/c/vendor
|
||||
// 3. b (non-recursive) - has ignored descendant in c/vendor
|
||||
// 4. c (non-recursive) - has ignored child vendor
|
||||
// 5. d (recursive) - no ignored descendants
|
||||
// 6. e (recursive) - no ignored descendants
|
||||
// 7. f (recursive) - no ignored descendants
|
||||
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
|
||||
// Verify vendor is not in watch paths
|
||||
for _, wp := range watchPaths {
|
||||
t.Assert(strings.Contains(wp.Path, "vendor"), false)
|
||||
}
|
||||
|
||||
// Find specific paths and verify their recursive flags
|
||||
foundF := false
|
||||
for _, wp := range watchPaths {
|
||||
if wp.Path == filepath.Join(tempDir, "f") {
|
||||
foundF = true
|
||||
t.Assert(wp.Recursive, true) // f should be recursive (no ignored descendants)
|
||||
}
|
||||
}
|
||||
t.Assert(foundF, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_EmptyDirectory(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create an empty temporary directory
|
||||
tempDir := gfile.Temp("gf_run_test_empty")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
gfile.Mkdir(tempDir)
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Empty directory should be watched recursively (no ignored descendants)
|
||||
t.Assert(len(watchPaths), 1)
|
||||
t.Assert(watchPaths[0].Path, tempDir)
|
||||
t.Assert(watchPaths[0].Recursive, true)
|
||||
})
|
||||
}
|
||||
@ -104,6 +104,10 @@ var (
|
||||
"smallmoney": {
|
||||
Type: "float64",
|
||||
},
|
||||
"uuid": {
|
||||
Type: "uuid.UUID",
|
||||
Import: "github.com/google/uuid",
|
||||
},
|
||||
}
|
||||
|
||||
// tablewriter Options
|
||||
|
||||
@ -98,7 +98,6 @@ func generateStructFieldDefinition(
|
||||
err error
|
||||
localTypeName gdb.LocalType
|
||||
localTypeNameStr string
|
||||
jsonTag = gstr.CaseConvert(field.Name, gstr.CaseTypeMatch(in.JsonCase))
|
||||
)
|
||||
|
||||
if in.TypeMapping != nil && len(in.TypeMapping) > 0 {
|
||||
@ -156,6 +155,8 @@ func generateStructFieldDefinition(
|
||||
" #" + formatFieldName(newFiledName, FieldNameCaseCamel),
|
||||
" #" + localTypeNameStr,
|
||||
}
|
||||
|
||||
jsonTag := gstr.CaseConvert(newFiledName, gstr.CaseTypeMatch(in.JsonCase))
|
||||
attrLines = append(attrLines, fmt.Sprintf(` #%sjson:"%s"`, tagKey, jsonTag))
|
||||
// orm tag
|
||||
if !in.IsDo {
|
||||
|
||||
236
cmd/gf/internal/cmd/geninit/geninit.go
Normal file
236
cmd/gf/internal/cmd/geninit/geninit.go
Normal file
@ -0,0 +1,236 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// ProcessOptions contains options for the Process function
|
||||
type ProcessOptions struct {
|
||||
SelectVersion bool // Enable interactive version selection
|
||||
ModulePath string // Custom go.mod module path (e.g., github.com/xxx/xxx)
|
||||
UpgradeDeps bool // Upgrade dependencies to latest (go get -u ./...)
|
||||
}
|
||||
|
||||
// Process handles the template generation flow from remote repository
|
||||
func Process(ctx context.Context, repo, name string, opts *ProcessOptions) error {
|
||||
if opts == nil {
|
||||
opts = &ProcessOptions{}
|
||||
}
|
||||
|
||||
// 0. Check Go environment first
|
||||
mlog.Print("Checking Go environment...")
|
||||
goEnv, err := CheckGoEnv(ctx)
|
||||
if err != nil {
|
||||
mlog.Printf("Go environment check failed: %v", err)
|
||||
return err
|
||||
}
|
||||
mlog.Printf("Go environment OK (version: %s)", goEnv.GOVERSION)
|
||||
|
||||
// Check if this is a git subdirectory URL
|
||||
if IsSubdirRepo(repo) {
|
||||
return processGitSubdir(ctx, repo, name, opts)
|
||||
}
|
||||
|
||||
// Try Go module download first, fallback to git subdirectory if it fails
|
||||
// This handles edge cases where the heuristic may be incorrect
|
||||
err = processGoModule(ctx, repo, name, opts)
|
||||
if err != nil {
|
||||
mlog.Printf("Go module download failed, trying git subdirectory mode: %v", err)
|
||||
mlog.Print("Note: If this is a git subdirectory, you can force git mode by using a full git URL")
|
||||
|
||||
// If Go module download fails, try git subdirectory as fallback
|
||||
// This handles cases where the heuristic incorrectly classified a git subdir as Go module
|
||||
if IsSubdirRepo(repo) {
|
||||
mlog.Print("Falling back to git subdirectory download...")
|
||||
return processGitSubdir(ctx, repo, name, opts)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// processGoModule handles standard Go module download via go get
|
||||
func processGoModule(ctx context.Context, repo, name string, opts *ProcessOptions) error {
|
||||
// Extract module path (without version)
|
||||
modulePath := repo
|
||||
specifiedVersion := ""
|
||||
if gstr.Contains(repo, "@") {
|
||||
parts := gstr.Split(repo, "@")
|
||||
modulePath = parts[0]
|
||||
specifiedVersion = parts[1]
|
||||
}
|
||||
|
||||
// Default name to repo basename if empty
|
||||
if name == "" {
|
||||
name = filepath.Base(modulePath)
|
||||
}
|
||||
|
||||
// Determine the target module path for go.mod
|
||||
targetModulePath := name
|
||||
if opts.ModulePath != "" {
|
||||
targetModulePath = opts.ModulePath
|
||||
}
|
||||
|
||||
// 1. Determine version to use
|
||||
var targetVersion string
|
||||
if specifiedVersion != "" {
|
||||
// User specified version
|
||||
targetVersion = specifiedVersion
|
||||
mlog.Printf("Using specified version: %s", targetVersion)
|
||||
} else if opts.SelectVersion {
|
||||
// Interactive version selection
|
||||
mlog.Print("Fetching available versions...")
|
||||
versionInfo, err := GetModuleVersions(ctx, modulePath)
|
||||
if err != nil {
|
||||
mlog.Printf("Failed to get versions: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
targetVersion, err = SelectVersion(ctx, versionInfo.Versions, modulePath)
|
||||
if err != nil {
|
||||
mlog.Printf("Version selection failed: %v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Default: use latest version
|
||||
mlog.Print("Fetching latest version...")
|
||||
latest, err := GetLatestVersion(ctx, modulePath)
|
||||
if err != nil {
|
||||
mlog.Printf("Failed to get latest version, will try @latest tag: %v", err)
|
||||
targetVersion = "latest"
|
||||
} else {
|
||||
targetVersion = latest
|
||||
mlog.Printf("Latest version: %s", targetVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Download Template with determined version
|
||||
repoWithVersion := modulePath + "@" + targetVersion
|
||||
srcDir, err := downloadTemplate(ctx, repoWithVersion)
|
||||
if err != nil {
|
||||
mlog.Printf("Download failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
mlog.Debugf("Template located at: %s", srcDir)
|
||||
|
||||
// 3. Generate Project
|
||||
if err := generateProject(ctx, srcDir, name, modulePath, targetModulePath); err != nil {
|
||||
mlog.Printf("Generation failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. Handle dependencies
|
||||
var projectDir string
|
||||
if name == "." {
|
||||
projectDir = gfile.Pwd()
|
||||
} else {
|
||||
projectDir = filepath.Join(gfile.Pwd(), name)
|
||||
}
|
||||
if opts.UpgradeDeps {
|
||||
// Upgrade all dependencies to latest
|
||||
if err := upgradeDependencies(ctx, projectDir); err != nil {
|
||||
mlog.Printf("Failed to upgrade dependencies: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Default: just tidy dependencies
|
||||
if err := tidyDependencies(ctx, projectDir); err != nil {
|
||||
mlog.Printf("Failed to tidy dependencies: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processGitSubdir handles git subdirectory download via sparse checkout
|
||||
func processGitSubdir(ctx context.Context, repo, name string, opts *ProcessOptions) error {
|
||||
mlog.Print("Detected subdirectory URL, using git sparse checkout...")
|
||||
|
||||
// Check if git is available
|
||||
gitVersion, err := CheckGitEnv(ctx)
|
||||
if err != nil {
|
||||
mlog.Printf("Git is required for subdirectory templates: %v", err)
|
||||
return err
|
||||
}
|
||||
mlog.Printf("Git available (%s)", gitVersion)
|
||||
|
||||
// Download via git sparse checkout
|
||||
srcDir, gitInfo, err := downloadGitSubdir(ctx, repo)
|
||||
if err != nil {
|
||||
mlog.Printf("Git download failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Clean up temp directory after generation
|
||||
// The temp dir is parent of parent of srcDir (tempDir/repo/subpath)
|
||||
tempDir := filepath.Dir(filepath.Dir(srcDir))
|
||||
if tempDir != "" && gfile.Exists(tempDir) && gstr.Contains(tempDir, "gf-init-git") {
|
||||
defer func() {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory %s: %v", tempDir, err)
|
||||
} else {
|
||||
mlog.Debugf("Cleaned up temp directory: %s", tempDir)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Default name to subpath basename if empty
|
||||
if name == "" {
|
||||
name = filepath.Base(gitInfo.SubPath)
|
||||
}
|
||||
|
||||
// Get original module name from go.mod (might be "main" or something else)
|
||||
oldModule := GetModuleNameFromGoMod(srcDir)
|
||||
if oldModule == "" {
|
||||
// Fallback: construct from git info
|
||||
oldModule = gitInfo.Host + "/" + gitInfo.Owner + "/" + gitInfo.Repo + "/" + gitInfo.SubPath
|
||||
}
|
||||
|
||||
// Determine the target module path for go.mod
|
||||
targetModulePath := name
|
||||
if opts.ModulePath != "" {
|
||||
targetModulePath = opts.ModulePath
|
||||
}
|
||||
|
||||
mlog.Debugf("Template located at: %s", srcDir)
|
||||
mlog.Debugf("Original module: %s", oldModule)
|
||||
|
||||
// Generate Project
|
||||
if err := generateProject(ctx, srcDir, name, oldModule, targetModulePath); err != nil {
|
||||
mlog.Printf("Generation failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle dependencies
|
||||
var projectDir string
|
||||
if name == "." {
|
||||
projectDir = gfile.Pwd()
|
||||
} else {
|
||||
projectDir = filepath.Join(gfile.Pwd(), name)
|
||||
}
|
||||
if opts.UpgradeDeps {
|
||||
// Upgrade all dependencies to latest
|
||||
if err := upgradeDependencies(ctx, projectDir); err != nil {
|
||||
mlog.Printf("Failed to upgrade dependencies: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Default: just tidy dependencies
|
||||
if err := tidyDependencies(ctx, projectDir); err != nil {
|
||||
mlog.Printf("Failed to tidy dependencies: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
126
cmd/gf/internal/cmd/geninit/geninit_ast.go
Normal file
126
cmd/gf/internal/cmd/geninit/geninit_ast.go
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// ASTReplacer handles import path replacement using Go AST
|
||||
type ASTReplacer struct {
|
||||
oldModule string
|
||||
newModule string
|
||||
fset *token.FileSet
|
||||
}
|
||||
|
||||
// NewASTReplacer creates a new AST-based import replacer
|
||||
func NewASTReplacer(oldModule, newModule string) *ASTReplacer {
|
||||
return &ASTReplacer{
|
||||
oldModule: oldModule,
|
||||
newModule: newModule,
|
||||
fset: token.NewFileSet(),
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceInFile replaces import paths in a single Go file
|
||||
func (r *ASTReplacer) ReplaceInFile(ctx context.Context, filePath string) error {
|
||||
// Read file content
|
||||
content := gfile.GetContents(filePath)
|
||||
if content == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse the file
|
||||
file, err := parser.ParseFile(r.fset, filePath, content, parser.ParseComments)
|
||||
if err != nil {
|
||||
mlog.Debugf("Failed to parse %s: %v", filePath, err)
|
||||
return nil // Skip files that can't be parsed
|
||||
}
|
||||
|
||||
// Track if any changes were made
|
||||
changed := false
|
||||
|
||||
// Traverse and modify imports
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
switch x := n.(type) {
|
||||
case *ast.ImportSpec:
|
||||
if x.Path != nil {
|
||||
importPath := strings.Trim(x.Path.Value, `"`)
|
||||
if strings.HasPrefix(importPath, r.oldModule) {
|
||||
// Replace only the leading module prefix for clarity and correctness.
|
||||
newPath := r.newModule + strings.TrimPrefix(importPath, r.oldModule)
|
||||
x.Path.Value = `"` + newPath + `"`
|
||||
changed = true
|
||||
mlog.Debugf("Replaced import: %s -> %s in %s", importPath, newPath, filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write back to file
|
||||
var buf bytes.Buffer
|
||||
// Use default printer configuration to match gofmt output
|
||||
cfg := &printer.Config{}
|
||||
if err := cfg.Fprint(&buf, r.fset, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gfile.PutContents(filePath, buf.String())
|
||||
}
|
||||
|
||||
// ReplaceInDir replaces import paths in all Go files in a directory (recursively)
|
||||
func (r *ASTReplacer) ReplaceInDir(ctx context.Context, dir string) error {
|
||||
mlog.Printf("Replacing imports: %s -> %s", r.oldModule, r.newModule)
|
||||
|
||||
// Find all .go files
|
||||
files, err := findGoFiles(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if err := r.ReplaceInFile(ctx, file); err != nil {
|
||||
mlog.Printf("Failed to process %s: %v", file, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findGoFiles recursively finds all .go files in a directory
|
||||
func findGoFiles(dir string) ([]string, error) {
|
||||
var files []string
|
||||
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && strings.HasSuffix(path, ".go") {
|
||||
files = append(files, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return files, err
|
||||
}
|
||||
111
cmd/gf/internal/cmd/geninit/geninit_downloader.go
Normal file
111
cmd/gf/internal/cmd/geninit/geninit_downloader.go
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// downloadTemplate fetches the remote repository using go get
|
||||
func downloadTemplate(ctx context.Context, repo string) (string, error) {
|
||||
// 1. Create a temporary directory workspace
|
||||
tempDir := gfile.Temp("gf-init-cli")
|
||||
if tempDir == "" {
|
||||
return "", fmt.Errorf("failed to create temporary directory")
|
||||
}
|
||||
if err := gfile.Mkdir(tempDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory %s: %v", tempDir, err)
|
||||
}
|
||||
}() // Clean up the temp workspace
|
||||
|
||||
mlog.Debugf("Using temp workspace: %s", tempDir)
|
||||
|
||||
// 2. Initialize a temp go module to perform go get
|
||||
// We run commands inside the temp directory
|
||||
if err := runCmd(ctx, tempDir, "go", "mod", "init", "temp"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 3. Run go get <repo>
|
||||
// Try different version strategies: original -> @latest -> @master
|
||||
moduleName := repo
|
||||
if gstr.Contains(repo, "@") {
|
||||
moduleName = gstr.Split(repo, "@")[0]
|
||||
}
|
||||
|
||||
var downloadErrs []string
|
||||
versionsToTry := []string{repo}
|
||||
if !gstr.Contains(repo, "@") {
|
||||
versionsToTry = append(versionsToTry, repo+"@latest", repo+"@master")
|
||||
}
|
||||
|
||||
var successRepo string
|
||||
for _, tryRepo := range versionsToTry {
|
||||
mlog.Printf("Downloading template %s...", tryRepo)
|
||||
if err := runCmd(ctx, tempDir, "go", "get", tryRepo); err == nil {
|
||||
successRepo = tryRepo
|
||||
break
|
||||
} else {
|
||||
downloadErrs = append(downloadErrs, fmt.Sprintf("%s: %v", tryRepo, err))
|
||||
mlog.Debugf("Failed to download %s, trying next...", tryRepo)
|
||||
}
|
||||
}
|
||||
|
||||
if successRepo == "" {
|
||||
errMsg := "all download attempts failed"
|
||||
if len(downloadErrs) > 0 {
|
||||
errMsg = strings.Join(downloadErrs, "; ")
|
||||
}
|
||||
return "", fmt.Errorf("failed to download repo %s: %s", repo, errMsg)
|
||||
}
|
||||
|
||||
// 4. Find the local path using go list -m -json <repo>
|
||||
listCmd := exec.CommandContext(ctx, "go", "list", "-m", "-json", moduleName)
|
||||
listCmd.Dir = tempDir
|
||||
output, err := listCmd.Output()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
return "", fmt.Errorf("go list failed: %s", string(exitErr.Stderr))
|
||||
}
|
||||
return "", fmt.Errorf("failed to locate module path: %w", err)
|
||||
}
|
||||
|
||||
var modInfo struct {
|
||||
Dir string `json:"Dir"`
|
||||
}
|
||||
if err := json.Unmarshal(output, &modInfo); err != nil {
|
||||
return "", fmt.Errorf("failed to parse go list output: %w", err)
|
||||
}
|
||||
|
||||
if modInfo.Dir == "" {
|
||||
return "", fmt.Errorf("module directory not found for %s", repo)
|
||||
}
|
||||
|
||||
return modInfo.Dir, nil
|
||||
}
|
||||
|
||||
func runCmd(ctx context.Context, dir string, name string, args ...string) error {
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
90
cmd/gf/internal/cmd/geninit/geninit_env.go
Normal file
90
cmd/gf/internal/cmd/geninit/geninit_env.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// GoEnv represents Go environment variables
|
||||
type GoEnv struct {
|
||||
GOVERSION string `json:"GOVERSION"`
|
||||
GOROOT string `json:"GOROOT"`
|
||||
GOPATH string `json:"GOPATH"`
|
||||
GOMODCACHE string `json:"GOMODCACHE"`
|
||||
GOPROXY string `json:"GOPROXY"`
|
||||
GO111MODULE string `json:"GO111MODULE"`
|
||||
}
|
||||
|
||||
// CheckGoEnv verifies Go is installed and properly configured
|
||||
func CheckGoEnv(ctx context.Context) (*GoEnv, error) {
|
||||
// 1. Check if go binary exists
|
||||
goPath, err := exec.LookPath("go")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("go is not installed or not in PATH: %w", err)
|
||||
}
|
||||
mlog.Debugf("Found go binary at: %s", goPath)
|
||||
|
||||
// 2. Get go env as JSON
|
||||
cmd := exec.CommandContext(ctx, "go", "env", "-json")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
return nil, fmt.Errorf("go env failed: %s", string(exitErr.Stderr))
|
||||
}
|
||||
return nil, fmt.Errorf("failed to run go env: %w", err)
|
||||
}
|
||||
|
||||
// 3. Parse JSON output
|
||||
var env GoEnv
|
||||
if err := json.Unmarshal(output, &env); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse go env output: %w", err)
|
||||
}
|
||||
|
||||
// 4. Validate critical environment variables
|
||||
if env.GOROOT == "" {
|
||||
return nil, fmt.Errorf("GOROOT is not set")
|
||||
}
|
||||
if env.GOMODCACHE == "" && env.GOPATH == "" {
|
||||
return nil, fmt.Errorf("neither GOMODCACHE nor GOPATH is set")
|
||||
}
|
||||
|
||||
mlog.Debugf("Go Version: %s", env.GOVERSION)
|
||||
mlog.Debugf("GOROOT: %s", env.GOROOT)
|
||||
mlog.Debugf("GOMODCACHE: %s", env.GOMODCACHE)
|
||||
mlog.Debugf("GOPROXY: %s", env.GOPROXY)
|
||||
|
||||
return &env, nil
|
||||
}
|
||||
|
||||
// CheckGitEnv verifies Git is installed and returns its version
|
||||
func CheckGitEnv(ctx context.Context) (string, error) {
|
||||
// 1. Check if git binary exists
|
||||
gitPath, err := exec.LookPath("git")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("git is not installed or not in PATH: %w", err)
|
||||
}
|
||||
mlog.Debugf("Found git binary at: %s", gitPath)
|
||||
|
||||
// 2. Get git version
|
||||
cmd := exec.CommandContext(ctx, "git", "--version")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get git version: %w", err)
|
||||
}
|
||||
|
||||
version := strings.TrimSpace(string(output))
|
||||
mlog.Debugf("Git version: %s", version)
|
||||
|
||||
return version, nil
|
||||
}
|
||||
110
cmd/gf/internal/cmd/geninit/geninit_generator.go
Normal file
110
cmd/gf/internal/cmd/geninit/geninit_generator.go
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// generateProject copies the template to the destination and performs cleanup
|
||||
// oldModule: original module path from template
|
||||
// newModule: target module path for go.mod (can be different from project name)
|
||||
func generateProject(ctx context.Context, srcPath, name, oldModule, newModule string) error {
|
||||
pwd := gfile.Pwd()
|
||||
|
||||
dstPath := filepath.Join(pwd, name)
|
||||
if name == "." {
|
||||
dstPath = pwd
|
||||
}
|
||||
|
||||
if gfile.Exists(dstPath) && !gfile.IsEmpty(dstPath) {
|
||||
return fmt.Errorf("target directory %s is not empty", dstPath)
|
||||
}
|
||||
|
||||
mlog.Printf("Generating project in %s...", dstPath)
|
||||
|
||||
// 1. Copy files
|
||||
if err := gfile.Copy(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. Clean up .git directory
|
||||
gitDir := filepath.Join(dstPath, ".git")
|
||||
if gfile.Exists(gitDir) {
|
||||
if err := gfile.Remove(gitDir); err != nil {
|
||||
mlog.Debugf("Failed to remove .git directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Clean up go.work and go.work.sum (workspace files should not be in generated project)
|
||||
for _, workFile := range []string{"go.work", "go.work.sum"} {
|
||||
workPath := filepath.Join(dstPath, workFile)
|
||||
if gfile.Exists(workPath) {
|
||||
if err := gfile.Remove(workPath); err != nil {
|
||||
mlog.Printf("Failed to remove %s: %v", workFile, err)
|
||||
} else {
|
||||
mlog.Debugf("Removed %s", workFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Update go.mod module name
|
||||
goModPath := filepath.Join(dstPath, "go.mod")
|
||||
if gfile.Exists(goModPath) {
|
||||
content := gfile.GetContents(goModPath)
|
||||
lines := gstr.Split(content, "\n")
|
||||
if len(lines) > 0 && gstr.HasPrefix(lines[0], "module ") {
|
||||
lines[0] = "module " + newModule
|
||||
newContent := gstr.Join(lines, "\n")
|
||||
if err := gfile.PutContents(goModPath, newContent); err != nil {
|
||||
mlog.Printf("Failed to update go.mod: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Use AST to replace import paths in all Go files
|
||||
if oldModule != "" && oldModule != newModule {
|
||||
replacer := NewASTReplacer(oldModule, newModule)
|
||||
if err := replacer.ReplaceInDir(ctx, dstPath); err != nil {
|
||||
return fmt.Errorf("failed to replace imports: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
mlog.Print("Project generated successfully!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// tidyDependencies runs go mod tidy in the project directory
|
||||
func tidyDependencies(ctx context.Context, projectDir string) error {
|
||||
mlog.Print("Tidying dependencies (go mod tidy)...")
|
||||
if err := runCmd(ctx, projectDir, "go", "mod", "tidy"); err != nil {
|
||||
return fmt.Errorf("go mod tidy failed: %w", err)
|
||||
}
|
||||
mlog.Print("Dependencies tidied successfully!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeDependencies runs go get -u ./... to upgrade all dependencies to latest
|
||||
func upgradeDependencies(ctx context.Context, projectDir string) error {
|
||||
mlog.Print("Upgrading dependencies to latest (go get -u ./...)...")
|
||||
if err := runCmd(ctx, projectDir, "go", "get", "-u", "./..."); err != nil {
|
||||
return fmt.Errorf("go get -u failed: %w", err)
|
||||
}
|
||||
// Run tidy again after upgrade
|
||||
if err := runCmd(ctx, projectDir, "go", "mod", "tidy"); err != nil {
|
||||
return fmt.Errorf("go mod tidy after upgrade failed: %w", err)
|
||||
}
|
||||
mlog.Print("Dependencies upgraded successfully!")
|
||||
return nil
|
||||
}
|
||||
241
cmd/gf/internal/cmd/geninit/geninit_git_downloader.go
Normal file
241
cmd/gf/internal/cmd/geninit/geninit_git_downloader.go
Normal file
@ -0,0 +1,241 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// GitRepoInfo holds parsed git repository information
|
||||
type GitRepoInfo struct {
|
||||
Host string // e.g., github.com
|
||||
Owner string // e.g., gogf
|
||||
Repo string // e.g., examples
|
||||
Branch string // e.g., main (default: main)
|
||||
SubPath string // e.g., httpserver/jwt
|
||||
CloneURL string // e.g., https://github.com/gogf/examples.git
|
||||
}
|
||||
|
||||
// ParseGitURL parses a git URL and extracts repository info
|
||||
// Supports formats:
|
||||
// - github.com/owner/repo
|
||||
// - github.com/owner/repo/subdir/path
|
||||
// - github.com/owner/repo/tree/branch/subdir/path (from GitHub web URL)
|
||||
func ParseGitURL(url string) (*GitRepoInfo, error) {
|
||||
// Remove protocol prefix if present
|
||||
url = strings.TrimPrefix(url, "https://")
|
||||
url = strings.TrimPrefix(url, "http://")
|
||||
url = strings.TrimSuffix(url, ".git")
|
||||
|
||||
// Remove version suffix like @v1.0.0
|
||||
if idx := strings.Index(url, "@"); idx != -1 {
|
||||
url = url[:idx]
|
||||
}
|
||||
|
||||
parts := strings.Split(url, "/")
|
||||
if len(parts) < 3 {
|
||||
return nil, fmt.Errorf("invalid git URL: %s", url)
|
||||
}
|
||||
|
||||
info := &GitRepoInfo{
|
||||
Host: parts[0],
|
||||
Owner: parts[1],
|
||||
Repo: parts[2],
|
||||
Branch: "main", // default branch
|
||||
}
|
||||
|
||||
// Check for /tree/branch/ pattern (GitHub web URL)
|
||||
if len(parts) > 4 && parts[3] == "tree" {
|
||||
info.Branch = parts[4]
|
||||
if len(parts) > 5 {
|
||||
info.SubPath = strings.Join(parts[5:], "/")
|
||||
}
|
||||
} else if len(parts) > 3 {
|
||||
// Direct subpath: github.com/owner/repo/subdir/path
|
||||
info.SubPath = strings.Join(parts[3:], "/")
|
||||
}
|
||||
|
||||
info.CloneURL = fmt.Sprintf("https://%s/%s/%s.git", info.Host, info.Owner, info.Repo)
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// IsSubdirRepo checks if the URL points to a subdirectory of a repository
|
||||
// Returns false for Go module paths (which may have /vN suffix or nested module paths)
|
||||
// Note: This uses heuristics that may have false positives/negatives in edge cases
|
||||
func IsSubdirRepo(url string) bool {
|
||||
info, err := ParseGitURL(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if info.SubPath == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if this looks like a Go module path rather than a git subdirectory
|
||||
// Go modules can have nested paths like github.com/owner/repo/cmd/tool/v2
|
||||
// We should try to resolve it as a Go module first
|
||||
|
||||
// If the URL can be resolved as a Go module, it's not a subdir repo
|
||||
// We use a heuristic: check if the full path looks like a valid Go module
|
||||
// by checking if it ends with /vN (major version) or contains common module patterns
|
||||
|
||||
// Remove version suffix for checking
|
||||
cleanURL := url
|
||||
if before, _, ok := strings.Cut(url, "@"); ok {
|
||||
cleanURL = before
|
||||
}
|
||||
|
||||
// Check if the path ends with /vN (Go module major version)
|
||||
parts := strings.Split(cleanURL, "/")
|
||||
if len(parts) > 0 {
|
||||
lastPart := parts[len(parts)-1]
|
||||
if len(lastPart) >= 2 && lastPart[0] == 'v' {
|
||||
// Check if it's v2, v3, etc.
|
||||
if _, err := fmt.Sscanf(lastPart, "v%d", new(int)); err == nil {
|
||||
// This looks like a Go module with major version suffix
|
||||
// It could be either a versioned module or a subdir ending in vN
|
||||
// We'll treat it as a Go module and let go get handle it
|
||||
mlog.Debugf("URL %s detected as Go module (ends with /vN)", url)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For GitHub URLs, check if the subpath could be a nested Go module
|
||||
// Common patterns: cmd/*, internal/*, pkg/*, contrib/*
|
||||
subPathParts := strings.Split(info.SubPath, "/")
|
||||
if len(subPathParts) > 0 {
|
||||
firstPart := subPathParts[0]
|
||||
// These are common Go module nesting patterns
|
||||
if firstPart == "cmd" || firstPart == "contrib" || firstPart == "tools" {
|
||||
// This might be a nested Go module, not a simple subdirectory
|
||||
// Let go get try first
|
||||
mlog.Debugf("URL %s detected as Go module (starts with common pattern)", url)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
mlog.Debugf("URL %s detected as git subdirectory", url)
|
||||
return true
|
||||
}
|
||||
|
||||
// downloadGitSubdir downloads a subdirectory from a git repository using sparse checkout
|
||||
func downloadGitSubdir(ctx context.Context, repoURL string) (string, *GitRepoInfo, error) {
|
||||
info, err := ParseGitURL(repoURL)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if info.SubPath == "" {
|
||||
return "", nil, fmt.Errorf("not a subdirectory URL: %s", repoURL)
|
||||
}
|
||||
|
||||
// Create temp directory for clone
|
||||
tempDir := gfile.Temp("gf-init-git")
|
||||
if tempDir == "" {
|
||||
return "", nil, fmt.Errorf("failed to create temporary directory")
|
||||
}
|
||||
if err := gfile.Mkdir(tempDir); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
cloneDir := filepath.Join(tempDir, info.Repo)
|
||||
mlog.Debugf("Using git temp workspace: %s", tempDir)
|
||||
mlog.Printf("Cloning %s (sparse checkout: %s)...", info.CloneURL, info.SubPath)
|
||||
|
||||
// 1. Clone with no checkout, filter, and sparse
|
||||
if err := runCmd(ctx, tempDir, "git", "clone", "--filter=blob:none", "--no-checkout", "--sparse", info.CloneURL); err != nil {
|
||||
// Fallback: try without filter for older git versions
|
||||
mlog.Debugf("Sparse clone failed, trying full clone...")
|
||||
if err := gfile.Remove(cloneDir); err != nil {
|
||||
mlog.Debugf("Failed to remove clone directory: %v", err)
|
||||
}
|
||||
if err := runCmd(ctx, tempDir, "git", "clone", "--no-checkout", info.CloneURL); err != nil {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("git clone failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Set sparse-checkout to the subpath
|
||||
if err := runCmd(ctx, cloneDir, "git", "sparse-checkout", "set", info.SubPath); err != nil {
|
||||
// Fallback for older git: use sparse-checkout init + set
|
||||
mlog.Debugf("sparse-checkout set failed, trying legacy method...")
|
||||
if err := runCmd(ctx, cloneDir, "git", "sparse-checkout", "init", "--cone"); err != nil {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("git sparse-checkout init (legacy) failed: %w", err)
|
||||
}
|
||||
if err := runCmd(ctx, cloneDir, "git", "sparse-checkout", "set", info.SubPath); err != nil {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("git sparse-checkout set (legacy) failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Checkout the branch
|
||||
if err := runCmd(ctx, cloneDir, "git", "checkout", info.Branch); err != nil {
|
||||
// Try master if main fails
|
||||
if info.Branch == "main" {
|
||||
mlog.Debugf("Branch 'main' not found, trying 'master'...")
|
||||
info.Branch = "master"
|
||||
if err := runCmd(ctx, cloneDir, "git", "checkout", "master"); err != nil {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("git checkout failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("git checkout failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the path to the subdirectory
|
||||
subDirPath := filepath.Join(cloneDir, info.SubPath)
|
||||
if !gfile.Exists(subDirPath) {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("subdirectory not found: %s", info.SubPath)
|
||||
}
|
||||
|
||||
mlog.Debugf("Subdirectory located at: %s", subDirPath)
|
||||
return subDirPath, info, nil
|
||||
}
|
||||
|
||||
// GetModuleNameFromGoMod reads module name from go.mod file
|
||||
func GetModuleNameFromGoMod(dir string) string {
|
||||
goModPath := filepath.Join(dir, "go.mod")
|
||||
if !gfile.Exists(goModPath) {
|
||||
return ""
|
||||
}
|
||||
|
||||
content := gfile.GetContents(goModPath)
|
||||
lines := gstr.Split(content, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if after, ok := strings.CutPrefix(line, "module "); ok {
|
||||
return strings.TrimSpace(after)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
99
cmd/gf/internal/cmd/geninit/geninit_selector.go
Normal file
99
cmd/gf/internal/cmd/geninit/geninit_selector.go
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// SelectVersion prompts user to select a version interactively
|
||||
func SelectVersion(ctx context.Context, versions []string, modulePath string) (string, error) {
|
||||
if len(versions) == 0 {
|
||||
return "", fmt.Errorf("no versions available for selection")
|
||||
}
|
||||
|
||||
if len(versions) == 1 {
|
||||
mlog.Printf("Only one version available: %s", versions[0])
|
||||
return versions[0], nil
|
||||
}
|
||||
|
||||
// Display available versions
|
||||
fmt.Printf("\nAvailable versions for %s:\n", modulePath)
|
||||
fmt.Println(strings.Repeat("-", 40))
|
||||
|
||||
// Show versions with index (newest first)
|
||||
maxDisplay := 20 // Limit display to avoid overwhelming output
|
||||
displayCount := len(versions)
|
||||
if displayCount > maxDisplay {
|
||||
displayCount = maxDisplay
|
||||
}
|
||||
|
||||
for i := 0; i < displayCount; i++ {
|
||||
marker := ""
|
||||
if i == 0 {
|
||||
marker = " (latest)"
|
||||
}
|
||||
fmt.Printf(" [%2d] %s%s\n", i+1, versions[i], marker)
|
||||
}
|
||||
|
||||
if len(versions) > maxDisplay {
|
||||
fmt.Printf(" ... and %d more versions\n", len(versions)-maxDisplay)
|
||||
}
|
||||
|
||||
fmt.Println(strings.Repeat("-", 40))
|
||||
|
||||
// Prompt for selection
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Printf("Select version [1-%d] or enter version string (default: 1 for latest): ", displayCount)
|
||||
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read input: %w", err)
|
||||
}
|
||||
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
// Default to latest
|
||||
if input == "" {
|
||||
fmt.Printf("Selected: %s (latest)\n", versions[0])
|
||||
return versions[0], nil
|
||||
}
|
||||
|
||||
// Try parsing as number first
|
||||
idx, err := strconv.Atoi(input)
|
||||
if err == nil {
|
||||
// Valid number - check if in range
|
||||
if idx >= 1 && idx <= len(versions) {
|
||||
// Allow selection from all versions, not just displayed ones
|
||||
selected := versions[idx-1]
|
||||
fmt.Printf("Selected: %s\n", selected)
|
||||
return selected, nil
|
||||
} else if idx < 1 || idx > displayCount {
|
||||
fmt.Printf("Invalid selection. Please enter a number between 1 and %d, or type a version string.\n", displayCount)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Try matching the input as a version string (e.g., "v1.2.3")
|
||||
for _, v := range versions {
|
||||
if v == input || strings.Contains(v, input) {
|
||||
fmt.Printf("Selected: %s\n", v)
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
fmt.Printf("Version '%s' not found. Please select by number or type a valid version string.\n", input)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
138
cmd/gf/internal/cmd/geninit/geninit_version.go
Normal file
138
cmd/gf/internal/cmd/geninit/geninit_version.go
Normal file
@ -0,0 +1,138 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// VersionInfo contains module version information
|
||||
type VersionInfo struct {
|
||||
Module string `json:"module"`
|
||||
Versions []string `json:"versions"`
|
||||
Latest string `json:"latest"`
|
||||
}
|
||||
|
||||
// GetModuleVersions fetches available versions for a Go module
|
||||
func GetModuleVersions(ctx context.Context, modulePath string) (*VersionInfo, error) {
|
||||
// Create a temporary directory for go list
|
||||
tempDir := gfile.Temp("gf-init-version")
|
||||
if tempDir == "" {
|
||||
return nil, fmt.Errorf("failed to create temporary directory for go list")
|
||||
}
|
||||
if err := gfile.Mkdir(tempDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Initialize a temp go module
|
||||
if err := runCmd(ctx, tempDir, "go", "mod", "init", "temp"); err != nil {
|
||||
return nil, fmt.Errorf("failed to init temp module: %w", err)
|
||||
}
|
||||
|
||||
// Get versions using go list -m -versions
|
||||
cmd := exec.CommandContext(ctx, "go", "list", "-m", "-versions", modulePath)
|
||||
cmd.Dir = tempDir
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// Try with @latest to see if module exists
|
||||
mlog.Debugf("go list -versions failed, trying @latest: %v", err)
|
||||
return getLatestOnly(ctx, tempDir, modulePath)
|
||||
}
|
||||
|
||||
// Parse output: "module/path v1.0.0 v1.1.0 v2.0.0"
|
||||
parts := strings.Fields(strings.TrimSpace(string(output)))
|
||||
if len(parts) < 1 {
|
||||
return nil, fmt.Errorf("no version information found for %s", modulePath)
|
||||
}
|
||||
|
||||
info := &VersionInfo{
|
||||
Module: parts[0],
|
||||
Versions: []string{},
|
||||
}
|
||||
|
||||
if len(parts) > 1 {
|
||||
info.Versions = parts[1:]
|
||||
// Sort versions in descending order (newest first)
|
||||
sort.Slice(info.Versions, func(i, j int) bool {
|
||||
return semver.Compare(info.Versions[i], info.Versions[j]) > 0
|
||||
})
|
||||
info.Latest = info.Versions[0]
|
||||
}
|
||||
|
||||
// If no tagged versions, try to get latest
|
||||
if len(info.Versions) == 0 {
|
||||
latestInfo, err := getLatestOnly(ctx, tempDir, modulePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info.Latest = latestInfo.Latest
|
||||
if latestInfo.Latest != "" {
|
||||
info.Versions = []string{latestInfo.Latest}
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// getLatestOnly gets only the latest version when go list -versions fails
|
||||
func getLatestOnly(ctx context.Context, tempDir, modulePath string) (*VersionInfo, error) {
|
||||
// Try go list -m modulePath@latest
|
||||
cmd := exec.CommandContext(ctx, "go", "list", "-m", "-json", modulePath+"@latest")
|
||||
cmd.Dir = tempDir
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// Try without @latest
|
||||
cmd = exec.CommandContext(ctx, "go", "list", "-m", "-json", modulePath)
|
||||
cmd.Dir = tempDir
|
||||
output, err = cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get module info for %s: %w", modulePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
var modInfo struct {
|
||||
Path string `json:"Path"`
|
||||
Version string `json:"Version"`
|
||||
}
|
||||
if err := json.Unmarshal(output, &modInfo); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse module info: %w", err)
|
||||
}
|
||||
|
||||
return &VersionInfo{
|
||||
Module: modInfo.Path,
|
||||
Versions: []string{modInfo.Version},
|
||||
Latest: modInfo.Version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetLatestVersion returns the latest version of a module
|
||||
func GetLatestVersion(ctx context.Context, modulePath string) (string, error) {
|
||||
info, err := GetModuleVersions(ctx, modulePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.Latest == "" {
|
||||
return "", fmt.Errorf("no version found for %s", modulePath)
|
||||
}
|
||||
return info.Latest, nil
|
||||
}
|
||||
@ -4,7 +4,7 @@ go 1.23.0
|
||||
|
||||
toolchain go1.24.6
|
||||
|
||||
require github.com/gogf/gf/v2 v2.9.5
|
||||
require github.com/gogf/gf/v2 v2.9.6
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
|
||||
@ -4,8 +4,8 @@ github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyM
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
|
||||
@ -7,245 +7,143 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// AnyAnyMap wraps map type `map[any]any` and provides more map features.
|
||||
type AnyAnyMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[any]any
|
||||
*KVMap[any, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewAnyAnyMap creates and returns an empty hash map.
|
||||
// The parameter `safe` is used to specify whether using map in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewAnyAnyMap(safe ...bool) *AnyAnyMap {
|
||||
return &AnyAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[any]any),
|
||||
m := &AnyAnyMap{
|
||||
KVMap: NewKVMap[any, any](safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NewAnyAnyMapFrom creates and returns a hash map from given map `data`.
|
||||
// Note that, the param `data` map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewAnyAnyMapFrom(data map[any]any, safe ...bool) *AnyAnyMap {
|
||||
return &AnyAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
m := &AnyAnyMap{
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *AnyAnyMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[any, any](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *AnyAnyMap) Iterator(f func(k any, v any) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *AnyAnyMap) Clone(safe ...bool) *AnyAnyMap {
|
||||
return NewFrom(m.MapCopy(), safe...)
|
||||
m.lazyInit()
|
||||
return NewAnyAnyMapFrom(m.MapCopy(), safe...)
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *AnyAnyMap) Map() map[any]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[any]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapCopy returns a shallow copy of the underlying data of the hash map.
|
||||
func (m *AnyAnyMap) MapCopy() map[any]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[any]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *AnyAnyMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[gconv.String(k)] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *AnyAnyMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// FilterNil deletes all key-value pair of which the value is nil.
|
||||
func (m *AnyAnyMap) FilterNil() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsNil(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterNil()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *AnyAnyMap) Set(key any, value any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]any)
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *AnyAnyMap) Sets(data map[any]any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *AnyAnyMap) Search(key any) (value any, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *AnyAnyMap) Get(key any) (value any) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *AnyAnyMap) Pop() (key, value any) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *AnyAnyMap) Pops(size int) map[any]any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[any]any, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if `value` is type of `func() interface {}`,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *AnyAnyMap) doSetWithLockCheck(key any, value any) any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]any)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *AnyAnyMap) GetOrSet(key any, value any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *AnyAnyMap) GetOrSetFunc(key any, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -255,55 +153,50 @@ func (m *AnyAnyMap) GetOrSetFunc(key any, f func() any) any {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *AnyAnyMap) GetOrSetFuncLock(key any, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVar(key any) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVarOrSet(key any, value any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVarOrSetFunc(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVarOrSetFuncLock(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *AnyAnyMap) SetIfNotExist(key any, value any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *AnyAnyMap) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -312,119 +205,76 @@ func (m *AnyAnyMap) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *AnyAnyMap) SetIfNotExistFuncLock(key any, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *AnyAnyMap) Remove(key any) (value any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *AnyAnyMap) Removes(keys []any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *AnyAnyMap) Keys() []any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var (
|
||||
keys = make([]any, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *AnyAnyMap) Values() []any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var (
|
||||
values = make([]any, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *AnyAnyMap) Contains(key any) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *AnyAnyMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *AnyAnyMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *AnyAnyMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[any]any)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *AnyAnyMap) Replace(data map[any]any) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *AnyAnyMap) LockFunc(f func(m map[any]any)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *AnyAnyMap) RLockFunc(f func(m map[any]any)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -441,19 +291,8 @@ func (m *AnyAnyMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -461,79 +300,40 @@ func (m *AnyAnyMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m AnyAnyMap) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *AnyAnyMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]any)
|
||||
}
|
||||
var data map[string]any
|
||||
if err := json.UnmarshalUseNumber(b, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *AnyAnyMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]any)
|
||||
}
|
||||
for k, v := range gconv.Map(value) {
|
||||
m.data[k] = v
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *AnyAnyMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &AnyAnyMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[any, any]),
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[any]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = deepcopy.Copy(v)
|
||||
}
|
||||
return NewFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *AnyAnyMap) IsSubOf(other *AnyAnyMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -541,22 +341,6 @@ func (m *AnyAnyMap) IsSubOf(other *AnyAnyMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *AnyAnyMap) Diff(other *AnyAnyMap) (addedKeys, removedKeys, updatedKeys []any) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if !reflect.DeepEqual(m.data[key], other.data[key]) {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
@ -8,244 +8,143 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// IntAnyMap implements map[int]any with RWMutex that has switch.
|
||||
type IntAnyMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[int]any
|
||||
*KVMap[int, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewIntAnyMap returns an empty IntAnyMap object.
|
||||
// The parameter `safe` is used to specify whether using map in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntAnyMap(safe ...bool) *IntAnyMap {
|
||||
return &IntAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[int]any),
|
||||
m := &IntAnyMap{
|
||||
KVMap: NewKVMap[int, any](safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NewIntAnyMapFrom creates and returns a hash map from given map `data`.
|
||||
// Note that, the param `data` map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewIntAnyMapFrom(data map[int]any, safe ...bool) *IntAnyMap {
|
||||
return &IntAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
m := &IntAnyMap{
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *IntAnyMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[int, any](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *IntAnyMap) Iterator(f func(k int, v any) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *IntAnyMap) Clone() *IntAnyMap {
|
||||
return NewIntAnyMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *IntAnyMap) Clone(safe ...bool) *IntAnyMap {
|
||||
m.lazyInit()
|
||||
return NewIntAnyMapFrom(m.MapCopy(), safe...)
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *IntAnyMap) Map() map[int]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[int]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *IntAnyMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[gconv.String(k)] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *IntAnyMap) MapCopy() map[int]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *IntAnyMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// FilterNil deletes all key-value pair of which the value is nil.
|
||||
func (m *IntAnyMap) FilterNil() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsNil(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterNil()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *IntAnyMap) Set(key int, val any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]any)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *IntAnyMap) Sets(data map[int]any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *IntAnyMap) Search(key int) (value any, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *IntAnyMap) Get(key int) (value any) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *IntAnyMap) Pop() (key int, value any) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *IntAnyMap) Pops(size int) map[int]any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[int]any, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if `value` is type of `func() interface {}`,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *IntAnyMap) doSetWithLockCheck(key int, value any) any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]any)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *IntAnyMap) GetOrSet(key int, value any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist and returns this value.
|
||||
func (m *IntAnyMap) GetOrSetFunc(key int, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -254,55 +153,50 @@ func (m *IntAnyMap) GetOrSetFunc(key int, f func() any) any {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *IntAnyMap) GetOrSetFuncLock(key int, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVar(key int) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetVarOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVarOrSet(key int, value any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVarOrSetFunc(key int, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVarOrSetFuncLock(key int, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntAnyMap) SetIfNotExist(key int, value any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntAnyMap) SetIfNotExistFunc(key int, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -311,119 +205,76 @@ func (m *IntAnyMap) SetIfNotExistFunc(key int, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *IntAnyMap) SetIfNotExistFuncLock(key int, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *IntAnyMap) Removes(keys []int) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *IntAnyMap) Remove(key int) (value any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *IntAnyMap) Keys() []int {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]int, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *IntAnyMap) Values() []any {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]any, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *IntAnyMap) Contains(key int) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *IntAnyMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *IntAnyMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *IntAnyMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[int]any)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *IntAnyMap) Replace(data map[int]any) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *IntAnyMap) LockFunc(f func(m map[int]any)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *IntAnyMap) RLockFunc(f func(m map[int]any)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -440,19 +291,8 @@ func (m *IntAnyMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *IntAnyMap) Merge(other *IntAnyMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -460,81 +300,40 @@ func (m *IntAnyMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m IntAnyMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *IntAnyMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]any)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *IntAnyMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]any)
|
||||
}
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data)
|
||||
default:
|
||||
for k, v := range gconv.Map(value) {
|
||||
m.data[gconv.Int(k)] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *IntAnyMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &IntAnyMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[int, any]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = deepcopy.Copy(v)
|
||||
}
|
||||
return NewIntAnyMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *IntAnyMap) IsSubOf(other *IntAnyMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -542,22 +341,6 @@ func (m *IntAnyMap) IsSubOf(other *IntAnyMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *IntAnyMap) Diff(other *IntAnyMap) (addedKeys, removedKeys, updatedKeys []int) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if !reflect.DeepEqual(m.data[key], other.data[key]) {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
@ -6,17 +6,12 @@
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
import "sync"
|
||||
|
||||
// IntIntMap implements map[int]int with RWMutex that has switch.
|
||||
type IntIntMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[int]int
|
||||
*KVMap[int, int]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewIntIntMap returns an empty IntIntMap object.
|
||||
@ -24,8 +19,7 @@ type IntIntMap struct {
|
||||
// which is false in default.
|
||||
func NewIntIntMap(safe ...bool) *IntIntMap {
|
||||
return &IntIntMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[int]int),
|
||||
KVMap: NewKVMap[int, int](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,193 +28,109 @@ func NewIntIntMap(safe ...bool) *IntIntMap {
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewIntIntMapFrom(data map[int]int, safe ...bool) *IntIntMap {
|
||||
return &IntIntMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *IntIntMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[int, int](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *IntIntMap) Iterator(f func(k int, v int) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *IntIntMap) Clone() *IntIntMap {
|
||||
return NewIntIntMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *IntIntMap) Clone(safe ...bool) *IntIntMap {
|
||||
m.lazyInit()
|
||||
return &IntIntMap{KVMap: m.KVMap.Clone(safe...)}
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *IntIntMap) Map() map[int]int {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[int]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *IntIntMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[gconv.String(k)] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *IntIntMap) MapCopy() map[int]int {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *IntIntMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *IntIntMap) Set(key int, val int) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *IntIntMap) Sets(data map[int]int) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *IntIntMap) Search(key int) (value int, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *IntIntMap) Get(key int) (value int) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *IntIntMap) Pop() (key, value int) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *IntIntMap) Pops(size int) map[int]int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[int]int, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *IntIntMap) doSetWithLockCheck(key int, value int) int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *IntIntMap) GetOrSet(key int, value int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist and returns this value.
|
||||
func (m *IntIntMap) GetOrSetFunc(key int, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -229,41 +139,22 @@ func (m *IntIntMap) GetOrSetFunc(key int, f func() int) int {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntIntMap) SetIfNotExist(key int, value int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntIntMap) SetIfNotExistFunc(key int, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -272,126 +163,76 @@ func (m *IntIntMap) SetIfNotExistFunc(key int, f func() int) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *IntIntMap) SetIfNotExistFuncLock(key int, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *IntIntMap) Removes(keys []int) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *IntIntMap) Remove(key int) (value int) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *IntIntMap) Keys() []int {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]int, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *IntIntMap) Values() []int {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]int, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *IntIntMap) Contains(key int) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *IntIntMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *IntIntMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *IntIntMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[int]int)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *IntIntMap) Replace(data map[int]int) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *IntIntMap) LockFunc(f func(m map[int]int)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *IntIntMap) RLockFunc(f func(m map[int]int)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -408,19 +249,8 @@ func (m *IntIntMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *IntIntMap) Merge(other *IntIntMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -428,81 +258,40 @@ func (m *IntIntMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m IntIntMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *IntIntMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *IntIntMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data)
|
||||
default:
|
||||
for k, v := range gconv.Map(value) {
|
||||
m.data[gconv.Int(k)] = gconv.Int(v)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *IntIntMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &IntIntMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[int, int]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return NewIntIntMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *IntIntMap) IsSubOf(other *IntIntMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -510,22 +299,6 @@ func (m *IntIntMap) IsSubOf(other *IntIntMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *IntIntMap) Diff(other *IntIntMap) (addedKeys, removedKeys, updatedKeys []int) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if m.data[key] != other.data[key] {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
@ -7,16 +7,15 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// IntStrMap implements map[int]string with RWMutex that has switch.
|
||||
type IntStrMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[int]string
|
||||
*KVMap[int, string]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewIntStrMap returns an empty IntStrMap object.
|
||||
@ -24,8 +23,7 @@ type IntStrMap struct {
|
||||
// which is false in default.
|
||||
func NewIntStrMap(safe ...bool) *IntStrMap {
|
||||
return &IntStrMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[int]string),
|
||||
KVMap: NewKVMap[int, string](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,193 +32,109 @@ func NewIntStrMap(safe ...bool) *IntStrMap {
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewIntStrMapFrom(data map[int]string, safe ...bool) *IntStrMap {
|
||||
return &IntStrMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *IntStrMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[int, string](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *IntStrMap) Iterator(f func(k int, v string) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *IntStrMap) Clone() *IntStrMap {
|
||||
return NewIntStrMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *IntStrMap) Clone(safe ...bool) *IntStrMap {
|
||||
m.lazyInit()
|
||||
return &IntStrMap{KVMap: m.KVMap.Clone(safe...)}
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *IntStrMap) Map() map[int]string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[int]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *IntStrMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[gconv.String(k)] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *IntStrMap) MapCopy() map[int]string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *IntStrMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *IntStrMap) Set(key int, val string) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *IntStrMap) Sets(data map[int]string) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *IntStrMap) Search(key int) (value string, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *IntStrMap) Get(key int) (value string) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *IntStrMap) Pop() (key int, value string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *IntStrMap) Pops(size int) map[int]string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[int]string, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *IntStrMap) doSetWithLockCheck(key int, value string) string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *IntStrMap) GetOrSet(key int, value string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist and returns this value.
|
||||
func (m *IntStrMap) GetOrSetFunc(key int, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -229,41 +143,22 @@ func (m *IntStrMap) GetOrSetFunc(key int, f func() string) string {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *IntStrMap) GetOrSetFuncLock(key int, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntStrMap) SetIfNotExist(key int, value string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntStrMap) SetIfNotExistFunc(key int, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -272,126 +167,76 @@ func (m *IntStrMap) SetIfNotExistFunc(key int, f func() string) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *IntStrMap) SetIfNotExistFuncLock(key int, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *IntStrMap) Removes(keys []int) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *IntStrMap) Remove(key int) (value string) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *IntStrMap) Keys() []int {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]int, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *IntStrMap) Values() []string {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]string, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *IntStrMap) Contains(key int) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *IntStrMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *IntStrMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *IntStrMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[int]string)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *IntStrMap) Replace(data map[int]string) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *IntStrMap) LockFunc(f func(m map[int]string)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *IntStrMap) RLockFunc(f func(m map[int]string)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -408,19 +253,8 @@ func (m *IntStrMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *IntStrMap) Merge(other *IntStrMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -428,81 +262,40 @@ func (m *IntStrMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m IntStrMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *IntStrMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *IntStrMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data)
|
||||
default:
|
||||
for k, v := range gconv.Map(value) {
|
||||
m.data[gconv.Int(k)] = gconv.String(v)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *IntStrMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &IntStrMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[int, string]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return NewIntStrMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *IntStrMap) IsSubOf(other *IntStrMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -510,22 +303,6 @@ func (m *IntStrMap) IsSubOf(other *IntStrMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *IntStrMap) Diff(other *IntStrMap) (addedKeys, removedKeys, updatedKeys []int) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if m.data[key] != other.data[key] {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
582
container/gmap/gmap_hash_k_v_map.go
Normal file
582
container/gmap/gmap_hash_k_v_map.go
Normal file
@ -0,0 +1,582 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// KVMap wraps map type `map[K]V` and provides more map features.
|
||||
type KVMap[K comparable, V any] struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[K]V
|
||||
}
|
||||
|
||||
// NewKVMap creates and returns an empty hash map.
|
||||
// The parameter `safe` is used to specify whether to use the map in concurrent-safety mode,
|
||||
// which is false by default.
|
||||
func NewKVMap[K comparable, V any](safe ...bool) *KVMap[K, V] {
|
||||
return NewKVMapFrom(make(map[K]V), safe...)
|
||||
}
|
||||
|
||||
// NewKVMapFrom creates and returns a hash map from given map `data`.
|
||||
// Note that, the param `data` map will be set as the underlying data map (no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *KVMap[K, V] {
|
||||
m := &KVMap[K, V]{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *KVMap[K, V]) Iterator(f func(k K, v V) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *KVMap[K, V]) Clone(safe ...bool) *KVMap[K, V] {
|
||||
if len(safe) == 0 {
|
||||
return NewKVMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
}
|
||||
return NewKVMapFrom(m.MapCopy(), safe...)
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *KVMap[K, V]) Map() map[K]V {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[K]V, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// MapCopy returns a shallow copy of the underlying data of the hash map.
|
||||
func (m *KVMap[K, V]) MapCopy() map[K]V {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[K]V, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *KVMap[K, V]) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[gconv.String(k)] = v
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *KVMap[K, V]) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FilterNil deletes all key-value pair of which the value is nil.
|
||||
func (m *KVMap[K, V]) FilterNil() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsNil(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *KVMap[K, V]) Set(key K, value V) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *KVMap[K, V]) Sets(data map[K]V) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *KVMap[K, V]) Search(key K) (value V, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *KVMap[K, V]) Get(key K) (value V) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *KVMap[K, V]) Pop() (key K, value V) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *KVMap[K, V]) Pops(size int) map[K]V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[K]V, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *KVMap[K, V]) doSetWithLockCheck(key K, value V) (val V, ok bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v, true
|
||||
}
|
||||
|
||||
if any(value) != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value, false
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *KVMap[K, V]) GetOrSet(key K, value V) V {
|
||||
v, _ := m.doSetWithLockCheck(key, value)
|
||||
return v
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *KVMap[K, V]) GetOrSetFunc(key K, f func() V) V {
|
||||
v, _ := m.doSetWithLockCheck(key, f())
|
||||
return v
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *KVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
value := f()
|
||||
if any(value) != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *KVMap[K, V]) GetVar(key K) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *KVMap[K, V]) GetVarOrSet(key K, value V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *KVMap[K, V]) GetVarOrSetFunc(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *KVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *KVMap[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = value
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *KVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
|
||||
if !m.Contains(key) {
|
||||
return m.SetIfNotExist(key, f())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *KVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *KVMap[K, V]) Remove(key K) (value V) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *KVMap[K, V]) Removes(keys []K) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *KVMap[K, V]) Keys() []K {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var (
|
||||
keys = make([]K, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *KVMap[K, V]) Values() []V {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var (
|
||||
values = make([]V, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *KVMap[K, V]) Contains(key K) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *KVMap[K, V]) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *KVMap[K, V]) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *KVMap[K, V]) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[K]V)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *KVMap[K, V]) Replace(data map[K]V) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *KVMap[K, V]) LockFunc(f func(m map[K]V)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *KVMap[K, V]) RLockFunc(f func(m map[K]V)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *KVMap[K, V]) Flip() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n := make(map[K]V, len(m.data))
|
||||
for k, v := range m.data {
|
||||
var (
|
||||
k0 K
|
||||
v0 V
|
||||
)
|
||||
if err := gconv.Scan(v, &k0); err != nil {
|
||||
continue
|
||||
}
|
||||
if err := gconv.Scan(k, &v0); err != nil {
|
||||
continue
|
||||
}
|
||||
n[k0] = v0
|
||||
}
|
||||
m.data = n
|
||||
}
|
||||
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *KVMap[K, V]) Merge(other *KVMap[K, V]) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *KVMap[K, V]) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m KVMap[K, V]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *KVMap[K, V]) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
var data map[string]V
|
||||
if err := json.UnmarshalUseNumber(b, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gconv.Scan(data, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *KVMap[K, V]) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
data := gconv.Map(value)
|
||||
if err := gconv.Scan(data, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *KVMap[K, V]) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[K]V, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = deepcopy.Copy(v).(V)
|
||||
}
|
||||
return NewKVMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *KVMap[K, V]) IsSubOf(other *KVMap[K, V]) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(otherValue, value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
// The returned `addedKeys` are the keys that are in map `m` but not in map `other`.
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *KVMap[K, V]) Diff(other *KVMap[K, V]) (addedKeys, removedKeys, updatedKeys []K) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if !reflect.DeepEqual(m.data[key], other.data[key]) {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -8,71 +8,66 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// StrAnyMap implements map[string]any with RWMutex that has switch.
|
||||
type StrAnyMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[string]any
|
||||
*KVMap[string, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewStrAnyMap returns an empty StrAnyMap object.
|
||||
// The parameter `safe` is used to specify whether using map in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStrAnyMap(safe ...bool) *StrAnyMap {
|
||||
return &StrAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[string]any),
|
||||
m := &StrAnyMap{
|
||||
KVMap: NewKVMap[string, any](safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NewStrAnyMapFrom creates and returns a hash map from given map `data`.
|
||||
// Note that, the param `data` map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewStrAnyMapFrom(data map[string]any, safe ...bool) *StrAnyMap {
|
||||
return &StrAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
m := &StrAnyMap{
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *StrAnyMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[string, any](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *StrAnyMap) Iterator(f func(k string, v any) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *StrAnyMap) Clone() *StrAnyMap {
|
||||
return NewStrAnyMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *StrAnyMap) Clone(safe ...bool) *StrAnyMap {
|
||||
m.lazyInit()
|
||||
return NewStrAnyMapFrom(m.MapCopy(), safe...)
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *StrAnyMap) Map() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
@ -82,165 +77,74 @@ func (m *StrAnyMap) MapStrAny() map[string]any {
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *StrAnyMap) MapCopy() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *StrAnyMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// FilterNil deletes all key-value pair of which the value is nil.
|
||||
func (m *StrAnyMap) FilterNil() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsNil(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterNil()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *StrAnyMap) Set(key string, val any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]any)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *StrAnyMap) Sets(data map[string]any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *StrAnyMap) Search(key string) (value any, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *StrAnyMap) Get(key string) (value any) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *StrAnyMap) Pop() (key string, value any) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *StrAnyMap) Pops(size int) map[string]any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[string]any, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if `value` is type of `func() interface {}`,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *StrAnyMap) doSetWithLockCheck(key string, value any) any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]any)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *StrAnyMap) GetOrSet(key string, value any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *StrAnyMap) GetOrSetFunc(key string, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -250,55 +154,50 @@ func (m *StrAnyMap) GetOrSetFunc(key string, f func() any) any {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *StrAnyMap) GetOrSetFuncLock(key string, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVar(key string) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetVarOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVarOrSet(key string, value any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVarOrSetFunc(key string, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVarOrSetFuncLock(key string, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrAnyMap) SetIfNotExist(key string, value any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrAnyMap) SetIfNotExistFunc(key string, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -307,119 +206,76 @@ func (m *StrAnyMap) SetIfNotExistFunc(key string, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *StrAnyMap) SetIfNotExistFuncLock(key string, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *StrAnyMap) Removes(keys []string) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *StrAnyMap) Remove(key string) (value any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *StrAnyMap) Keys() []string {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]string, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *StrAnyMap) Values() []any {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]any, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *StrAnyMap) Contains(key string) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *StrAnyMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *StrAnyMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *StrAnyMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[string]any)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *StrAnyMap) Replace(data map[string]any) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *StrAnyMap) LockFunc(f func(m map[string]any)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *StrAnyMap) RLockFunc(f func(m map[string]any)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -436,19 +292,8 @@ func (m *StrAnyMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *StrAnyMap) Merge(other *StrAnyMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -456,71 +301,40 @@ func (m *StrAnyMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m StrAnyMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *StrAnyMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]any)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *StrAnyMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.data = gconv.Map(value)
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *StrAnyMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &StrAnyMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[string, any]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = deepcopy.Copy(v)
|
||||
}
|
||||
return NewStrAnyMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *StrAnyMap) IsSubOf(other *StrAnyMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -528,22 +342,6 @@ func (m *StrAnyMap) IsSubOf(other *StrAnyMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *StrAnyMap) Diff(other *StrAnyMap) (addedKeys, removedKeys, updatedKeys []string) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if !reflect.DeepEqual(m.data[key], other.data[key]) {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
@ -8,16 +8,15 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// StrIntMap implements map[string]int with RWMutex that has switch.
|
||||
type StrIntMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[string]int
|
||||
*KVMap[string, int]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewStrIntMap returns an empty StrIntMap object.
|
||||
@ -25,8 +24,7 @@ type StrIntMap struct {
|
||||
// which is false in default.
|
||||
func NewStrIntMap(safe ...bool) *StrIntMap {
|
||||
return &StrIntMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[string]int),
|
||||
KVMap: NewKVMap[string, int](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,195 +33,110 @@ func NewStrIntMap(safe ...bool) *StrIntMap {
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewStrIntMapFrom(data map[string]int, safe ...bool) *StrIntMap {
|
||||
return &StrIntMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *StrIntMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[string, int](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *StrIntMap) Iterator(f func(k string, v int) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *StrIntMap) Clone() *StrIntMap {
|
||||
return NewStrIntMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *StrIntMap) Clone(safe ...bool) *StrIntMap {
|
||||
m.lazyInit()
|
||||
return &StrIntMap{KVMap: m.KVMap.Clone(safe...)}
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *StrIntMap) Map() map[string]int {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[string]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *StrIntMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *StrIntMap) MapCopy() map[string]int {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *StrIntMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *StrIntMap) Set(key string, val int) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *StrIntMap) Sets(data map[string]int) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *StrIntMap) Search(key string) (value int, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *StrIntMap) Get(key string) (value int) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *StrIntMap) Pop() (key string, value int) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *StrIntMap) Pops(size int) map[string]int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[string]int, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *StrIntMap) doSetWithLockCheck(key string, value int) int {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
m.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *StrIntMap) GetOrSet(key string, value int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *StrIntMap) GetOrSetFunc(key string, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -233,41 +146,22 @@ func (m *StrIntMap) GetOrSetFunc(key string, f func() int) int {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *StrIntMap) GetOrSetFuncLock(key string, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrIntMap) SetIfNotExist(key string, value int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrIntMap) SetIfNotExistFunc(key string, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -276,126 +170,76 @@ func (m *StrIntMap) SetIfNotExistFunc(key string, f func() int) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *StrIntMap) SetIfNotExistFuncLock(key string, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *StrIntMap) Removes(keys []string) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *StrIntMap) Remove(key string) (value int) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *StrIntMap) Keys() []string {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]string, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *StrIntMap) Values() []int {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]int, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *StrIntMap) Contains(key string) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *StrIntMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *StrIntMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *StrIntMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[string]int)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *StrIntMap) Replace(data map[string]int) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *StrIntMap) LockFunc(f func(m map[string]int)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *StrIntMap) RLockFunc(f func(m map[string]int)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -412,19 +256,8 @@ func (m *StrIntMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *StrIntMap) Merge(other *StrIntMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -432,81 +265,40 @@ func (m *StrIntMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m StrIntMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *StrIntMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *StrIntMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data)
|
||||
default:
|
||||
for k, v := range gconv.Map(value) {
|
||||
m.data[k] = gconv.Int(v)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *StrIntMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &StrIntMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[string, int]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return NewStrIntMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *StrIntMap) IsSubOf(other *StrIntMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -514,22 +306,6 @@ func (m *StrIntMap) IsSubOf(other *StrIntMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *StrIntMap) Diff(other *StrIntMap) (addedKeys, removedKeys, updatedKeys []string) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if m.data[key] != other.data[key] {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
@ -7,17 +7,12 @@
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
import "sync"
|
||||
|
||||
// StrStrMap implements map[string]string with RWMutex that has switch.
|
||||
type StrStrMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[string]string
|
||||
*KVMap[string, string]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewStrStrMap returns an empty StrStrMap object.
|
||||
@ -25,8 +20,7 @@ type StrStrMap struct {
|
||||
// which is false in default.
|
||||
func NewStrStrMap(safe ...bool) *StrStrMap {
|
||||
return &StrStrMap{
|
||||
data: make(map[string]string),
|
||||
mu: rwmutex.Create(safe...),
|
||||
KVMap: NewKVMap[string, string](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,194 +29,110 @@ func NewStrStrMap(safe ...bool) *StrStrMap {
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewStrStrMapFrom(data map[string]string, safe ...bool) *StrStrMap {
|
||||
return &StrStrMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *StrStrMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[string, string](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *StrStrMap) Iterator(f func(k string, v string) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *StrStrMap) Clone() *StrStrMap {
|
||||
return NewStrStrMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *StrStrMap) Clone(safe ...bool) *StrStrMap {
|
||||
m.lazyInit()
|
||||
return &StrStrMap{KVMap: m.KVMap.Clone(safe...)}
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *StrStrMap) Map() map[string]string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[string]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *StrStrMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *StrStrMap) MapCopy() map[string]string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *StrStrMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *StrStrMap) Set(key string, val string) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]string)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *StrStrMap) Sets(data map[string]string) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *StrStrMap) Search(key string) (value string, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *StrStrMap) Get(key string) (value string) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *StrStrMap) Pop() (key, value string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *StrStrMap) Pops(size int) map[string]string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[string]string, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *StrStrMap) doSetWithLockCheck(key string, value string) string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]string)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *StrStrMap) GetOrSet(key string, value string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *StrStrMap) GetOrSetFunc(key string, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -232,41 +142,22 @@ func (m *StrStrMap) GetOrSetFunc(key string, f func() string) string {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *StrStrMap) GetOrSetFuncLock(key string, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]string)
|
||||
}
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrStrMap) SetIfNotExist(key string, value string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrStrMap) SetIfNotExistFunc(key string, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -275,126 +166,76 @@ func (m *StrStrMap) SetIfNotExistFunc(key string, f func() string) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *StrStrMap) SetIfNotExistFuncLock(key string, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]string)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *StrStrMap) Removes(keys []string) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *StrStrMap) Remove(key string) (value string) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *StrStrMap) Keys() []string {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]string, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *StrStrMap) Values() []string {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]string, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *StrStrMap) Contains(key string) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *StrStrMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *StrStrMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *StrStrMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[string]string)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *StrStrMap) Replace(data map[string]string) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *StrStrMap) LockFunc(f func(m map[string]string)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *StrStrMap) RLockFunc(f func(m map[string]string)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -411,19 +252,8 @@ func (m *StrStrMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *StrStrMap) Merge(other *StrStrMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -431,71 +261,40 @@ func (m *StrStrMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m StrStrMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *StrStrMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]string)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *StrStrMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.data = gconv.MapStrStr(value)
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *StrStrMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &StrStrMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[string, string]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return NewStrStrMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *StrStrMap) IsSubOf(other *StrStrMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -503,22 +302,6 @@ func (m *StrStrMap) IsSubOf(other *StrStrMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *StrStrMap) Diff(other *StrStrMap) (addedKeys, removedKeys, updatedKeys []string) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if m.data[key] != other.data[key] {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
654
container/gmap/gmap_list_k_v_map.go
Normal file
654
container/gmap/gmap_list_k_v_map.go
Normal file
@ -0,0 +1,654 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/container/glist"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// ListKVMap is a map that preserves insertion-order.
|
||||
//
|
||||
// It is backed by a hash table to store values and doubly-linked list to store ordering.
|
||||
//
|
||||
// Thread-safety is optional and controlled by the `safe` parameter during initialization.
|
||||
//
|
||||
// Reference: http://en.wikipedia.org/wiki/Associative_array
|
||||
type ListKVMap[K comparable, V any] struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[K]*glist.TElement[*gListKVMapNode[K, V]]
|
||||
list *glist.TList[*gListKVMapNode[K, V]]
|
||||
}
|
||||
|
||||
type gListKVMapNode[K comparable, V any] struct {
|
||||
key K
|
||||
value V
|
||||
}
|
||||
|
||||
// NewListKVMap returns an empty link map.
|
||||
// ListKVMap is backed by a hash table to store values and doubly-linked list to store ordering.
|
||||
// The parameter `safe` is used to specify whether using map in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewListKVMap[K comparable, V any](safe ...bool) *ListKVMap[K, V] {
|
||||
return &ListKVMap[K, V]{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[K]*glist.TElement[*gListKVMapNode[K, V]]),
|
||||
list: glist.NewT[*gListKVMapNode[K, V]](),
|
||||
}
|
||||
}
|
||||
|
||||
// NewListKVMapFrom returns a link map from given map `data`.
|
||||
// Note that, the param `data` map will be copied to the underlying data structure,
|
||||
// so changes to the original map will not affect the link map.
|
||||
func NewListKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *ListKVMap[K, V] {
|
||||
m := NewListKVMap[K, V](safe...)
|
||||
m.Sets(data)
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (m *ListKVMap[K, V]) Iterator(f func(key K, value V) bool) {
|
||||
m.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the map readonly in ascending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *ListKVMap[K, V]) IteratorAsc(f func(key K, value V) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
return f(e.Value.key, e.Value.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the map readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *ListKVMap[K, V]) IteratorDesc(f func(key K, value V) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if m.list != nil {
|
||||
m.list.IteratorDesc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
return f(e.Value.key, e.Value.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new link map with copy of current map data.
|
||||
func (m *ListKVMap[K, V]) Clone(safe ...bool) *ListKVMap[K, V] {
|
||||
return NewListKVMapFrom(m.Map(), safe...)
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *ListKVMap[K, V]) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *ListKVMap[K, V]) Replace(data map[K]V) {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
for key, value := range data {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Map returns a copy of the underlying data of the map.
|
||||
func (m *ListKVMap[K, V]) Map() map[K]V {
|
||||
m.mu.RLock()
|
||||
var data map[K]V
|
||||
if m.list != nil {
|
||||
data = make(map[K]V, len(m.data))
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
data[e.Value.key] = e.Value.value
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *ListKVMap[K, V]) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
var data map[string]any
|
||||
if m.list != nil {
|
||||
data = make(map[string]any, len(m.data))
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
data[gconv.String(e.Value.key)] = e.Value.value
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
func (m *ListKVMap[K, V]) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
if m.list != nil {
|
||||
var keys = make([]K, 0, m.list.Size())
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
if empty.IsEmpty(e.Value.value) {
|
||||
keys = append(keys, e.Value.key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if len(keys) > 0 {
|
||||
for _, key := range keys {
|
||||
if e, ok := m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Set sets key-value to the map.
|
||||
func (m *ListKVMap[K, V]) Set(key K, value V) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
} else {
|
||||
e.Value = &gListKVMapNode[K, V]{key, value}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the map.
|
||||
func (m *ListKVMap[K, V]) Sets(data map[K]V) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
for key, value := range data {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
} else {
|
||||
e.Value = &gListKVMapNode[K, V]{key, value}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *ListKVMap[K, V]) Search(key K) (value V, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.value
|
||||
found = ok
|
||||
}
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *ListKVMap[K, V]) Get(key K) (value V) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.value
|
||||
}
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *ListKVMap[K, V]) Pop() (key K, value V) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, e := range m.data {
|
||||
value = e.Value.value
|
||||
delete(m.data, k)
|
||||
m.list.Remove(e)
|
||||
return k, value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *ListKVMap[K, V]) Pops(size int) map[K]V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
index := 0
|
||||
newMap := make(map[K]V, size)
|
||||
for k, e := range m.data {
|
||||
value := e.Value.value
|
||||
delete(m.data, k)
|
||||
m.list.Remove(e)
|
||||
newMap[k] = value
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *ListKVMap[K, V]) doSetWithLockCheck(key K, value V) V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
return m.doSetWithLockCheckWithoutLock(key, value)
|
||||
}
|
||||
|
||||
func (m *ListKVMap[K, V]) doSetWithLockCheckWithoutLock(key K, value V) V {
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if e, ok := m.data[key]; ok {
|
||||
return e.Value.value
|
||||
}
|
||||
if any(value) != nil {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *ListKVMap[K, V]) GetOrSet(key K, value V) V {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *ListKVMap[K, V]) GetOrSetFunc(key K, f func() V) V {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the map.
|
||||
func (m *ListKVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if e, ok := m.data[key]; ok {
|
||||
return e.Value.value
|
||||
}
|
||||
value := f()
|
||||
if any(value) != nil {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListKVMap[K, V]) GetVar(key K) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetVarOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListKVMap[K, V]) GetVarOrSet(key K, value V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListKVMap[K, V]) GetVarOrSetFunc(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListKVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if _, ok := m.data[key]; ok {
|
||||
return false
|
||||
}
|
||||
if any(value) != nil {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if _, ok := m.data[key]; ok {
|
||||
return false
|
||||
}
|
||||
value := f()
|
||||
if any(value) != nil {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the map.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if _, ok := m.data[key]; ok {
|
||||
return false
|
||||
}
|
||||
value := f()
|
||||
if any(value) != nil {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *ListKVMap[K, V]) Remove(key K) (value V) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.value
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *ListKVMap[K, V]) Removes(keys []K) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
if e, ok := m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice in ascending order.
|
||||
func (m *ListKVMap[K, V]) Keys() []K {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]K, m.list.Len())
|
||||
index = 0
|
||||
)
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
keys[index] = e.Value.key
|
||||
index++
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *ListKVMap[K, V]) Values() []V {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]V, m.list.Len())
|
||||
index = 0
|
||||
)
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
values[index] = e.Value.value
|
||||
index++
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *ListKVMap[K, V]) Contains(key K) (ok bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *ListKVMap[K, V]) Size() (size int) {
|
||||
m.mu.RLock()
|
||||
size = len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *ListKVMap[K, V]) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *ListKVMap[K, V]) Flip() error {
|
||||
data := m.Map()
|
||||
m.Clear()
|
||||
for key, value := range data {
|
||||
var (
|
||||
newKey K
|
||||
newValue V
|
||||
)
|
||||
|
||||
if err := gconv.Scan(value, &newKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gconv.Scan(key, &newValue); err != nil {
|
||||
return err
|
||||
}
|
||||
m.Set(newKey, newValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge merges two link maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *ListKVMap[K, V]) Merge(other *ListKVMap[K, V]) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
var node *gListKVMapNode[K, V]
|
||||
other.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
node = e.Value
|
||||
if e, ok := m.data[node.key]; !ok {
|
||||
m.data[node.key] = m.list.PushBack(&gListKVMapNode[K, V]{node.key, node.value})
|
||||
} else {
|
||||
e.Value = &gListKVMapNode[K, V]{node.key, node.value}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *ListKVMap[K, V]) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m ListKVMap[K, V]) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
if m.data == nil {
|
||||
return []byte("{}"), nil
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteByte('{')
|
||||
m.Iterator(func(key K, value V) bool {
|
||||
valueBytes, valueJSONErr := json.Marshal(value)
|
||||
if valueJSONErr != nil {
|
||||
err = valueJSONErr
|
||||
return false
|
||||
}
|
||||
if buffer.Len() > 1 {
|
||||
buffer.WriteByte(',')
|
||||
}
|
||||
fmt.Fprintf(buffer, `"%v":%s`, key, valueBytes)
|
||||
return true
|
||||
})
|
||||
buffer.WriteByte('}')
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *ListKVMap[K, V]) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
var data map[string]V
|
||||
if err := json.UnmarshalUseNumber(b, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
var kvData map[K]V
|
||||
if err := gconv.Scan(data, &kvData); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range kvData {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
} else {
|
||||
e.Value = &gListKVMapNode[K, V]{key, value}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *ListKVMap[K, V]) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
var dataMap map[K]V
|
||||
if err = gconv.Scan(value, &dataMap); err != nil {
|
||||
return
|
||||
}
|
||||
for k, v := range dataMap {
|
||||
if e, ok := m.data[k]; !ok {
|
||||
m.data[k] = m.list.PushBack(&gListKVMapNode[K, V]{k, v})
|
||||
} else {
|
||||
e.Value = &gListKVMapNode[K, V]{k, v}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *ListKVMap[K, V]) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[K]V, len(m.data))
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
data[e.Value.key] = deepcopy.Copy(e.Value.value).(V)
|
||||
return true
|
||||
})
|
||||
}
|
||||
return NewListKVMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
@ -7,15 +7,9 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/glist"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
@ -27,15 +21,11 @@ import (
|
||||
//
|
||||
// Reference: http://en.wikipedia.org/wiki/Associative_array
|
||||
type ListMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[any]*glist.Element
|
||||
list *glist.List
|
||||
*ListKVMap[any, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
type gListMapNode struct {
|
||||
key any
|
||||
value any
|
||||
}
|
||||
type gListMapNode = gListKVMapNode[any, any]
|
||||
|
||||
// NewListMap returns an empty link map.
|
||||
// ListMap is backed by a hash table to store values and doubly-linked list to store ordering.
|
||||
@ -43,9 +33,7 @@ type gListMapNode struct {
|
||||
// which is false in default.
|
||||
func NewListMap(safe ...bool) *ListMap {
|
||||
return &ListMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[any]*glist.Element),
|
||||
list: glist.New(),
|
||||
ListKVMap: NewListKVMap[any, any](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,6 +46,15 @@ func NewListMapFrom(data map[any]any, safe ...bool) *ListMap {
|
||||
return m
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the list map.
|
||||
func (m *ListMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.ListKVMap == nil {
|
||||
m.ListKVMap = NewListKVMap[any, any](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (m *ListMap) Iterator(f func(key, value any) bool) {
|
||||
m.IteratorAsc(f)
|
||||
@ -66,29 +63,15 @@ func (m *ListMap) Iterator(f func(key, value any) bool) {
|
||||
// IteratorAsc iterates the map readonly in ascending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *ListMap) IteratorAsc(f func(key any, value any) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if m.list != nil {
|
||||
var node *gListMapNode
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
return f(node.key, node.value)
|
||||
})
|
||||
}
|
||||
m.lazyInit()
|
||||
m.ListKVMap.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the map readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *ListMap) IteratorDesc(f func(key any, value any) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if m.list != nil {
|
||||
var node *gListMapNode
|
||||
m.list.IteratorDesc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
return f(node.key, node.value)
|
||||
})
|
||||
}
|
||||
m.lazyInit()
|
||||
m.ListKVMap.IteratorDesc(f)
|
||||
}
|
||||
|
||||
// Clone returns a new link map with copy of current map data.
|
||||
@ -98,232 +81,85 @@ func (m *ListMap) Clone(safe ...bool) *ListMap {
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *ListMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *ListMap) Replace(data map[any]any) {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
for key, value := range data {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{key, value}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.Replace(data)
|
||||
}
|
||||
|
||||
// Map returns a copy of the underlying data of the map.
|
||||
func (m *ListMap) Map() map[any]any {
|
||||
m.mu.RLock()
|
||||
var node *gListMapNode
|
||||
var data map[any]any
|
||||
if m.list != nil {
|
||||
data = make(map[any]any, len(m.data))
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
data[node.key] = node.value
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *ListMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
var node *gListMapNode
|
||||
var data map[string]any
|
||||
if m.list != nil {
|
||||
data = make(map[string]any, len(m.data))
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
data[gconv.String(node.key)] = node.value
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
func (m *ListMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
if m.list != nil {
|
||||
var (
|
||||
keys = make([]any, 0)
|
||||
node *gListMapNode
|
||||
)
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
if empty.IsEmpty(node.value) {
|
||||
keys = append(keys, node.key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(keys) > 0 {
|
||||
for _, key := range keys {
|
||||
if e, ok := m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// Set sets key-value to the map.
|
||||
func (m *ListMap) Set(key any, value any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{key, value}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.Set(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the map.
|
||||
func (m *ListMap) Sets(data map[any]any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
for key, value := range data {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{key, value}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *ListMap) Search(key any) (value any, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
found = ok
|
||||
}
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *ListMap) Get(key any) (value any) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
}
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *ListMap) Pop() (key, value any) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, e := range m.data {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
delete(m.data, k)
|
||||
m.list.Remove(e)
|
||||
return k, value
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *ListMap) Pops(size int) map[any]any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
index := 0
|
||||
newMap := make(map[any]any, size)
|
||||
for k, e := range m.data {
|
||||
value := e.Value.(*gListMapNode).value
|
||||
delete(m.data, k)
|
||||
m.list.Remove(e)
|
||||
newMap[k] = value
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if `value` is type of `func() interface {}`,
|
||||
// it will be executed with mutex.Lock of the map,
|
||||
// and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *ListMap) doSetWithLockCheck(key any, value any) any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
if e, ok := m.data[key]; ok {
|
||||
return e.Value.(*gListMapNode).value
|
||||
}
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value != nil {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
}
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *ListMap) GetOrSet(key any, value any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *ListMap) GetOrSetFunc(key any, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -333,55 +169,50 @@ func (m *ListMap) GetOrSetFunc(key any, f func() any) any {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the map.
|
||||
func (m *ListMap) GetOrSetFuncLock(key any, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVar(key any) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetVarOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVarOrSet(key any, value any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVarOrSetFunc(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVarOrSetFuncLock(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *ListMap) SetIfNotExist(key any, value any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *ListMap) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -390,100 +221,52 @@ func (m *ListMap) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the map.
|
||||
func (m *ListMap) SetIfNotExistFuncLock(key any, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *ListMap) Remove(key any) (value any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *ListMap) Removes(keys []any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
if e, ok := m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice in ascending order.
|
||||
func (m *ListMap) Keys() []any {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]any, m.list.Len())
|
||||
index = 0
|
||||
)
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
keys[index] = e.Value.(*gListMapNode).key
|
||||
index++
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *ListMap) Values() []any {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]any, m.list.Len())
|
||||
index = 0
|
||||
)
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
values[index] = e.Value.(*gListMapNode).value
|
||||
index++
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *ListMap) Contains(key any) (ok bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *ListMap) Size() (size int) {
|
||||
m.mu.RLock()
|
||||
size = len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *ListMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -498,90 +281,35 @@ func (m *ListMap) Flip() {
|
||||
// Merge merges two link maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *ListMap) Merge(other *ListMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
var node *gListMapNode
|
||||
other.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
if e, ok := m.data[node.key]; !ok {
|
||||
m.data[node.key] = m.list.PushBack(&gListMapNode{node.key, node.value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{node.key, node.value}
|
||||
}
|
||||
return true
|
||||
})
|
||||
m.lazyInit()
|
||||
other.lazyInit()
|
||||
m.ListKVMap.Merge(other.ListKVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *ListMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m ListMap) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
if m.data == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteByte('{')
|
||||
m.Iterator(func(key, value any) bool {
|
||||
valueBytes, valueJSONErr := json.Marshal(value)
|
||||
if valueJSONErr != nil {
|
||||
err = valueJSONErr
|
||||
return false
|
||||
}
|
||||
if buffer.Len() > 1 {
|
||||
buffer.WriteByte(',')
|
||||
}
|
||||
fmt.Fprintf(buffer, `"%v":%s`, key, valueBytes)
|
||||
return true
|
||||
})
|
||||
buffer.WriteByte('}')
|
||||
return buffer.Bytes(), nil
|
||||
return m.ListKVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *ListMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
var data map[string]any
|
||||
if err := json.UnmarshalUseNumber(b, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range data {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{key, value}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *ListMap) UnmarshalValue(value any) (err error) {
|
||||
m.lazyInit()
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
|
||||
for k, v := range gconv.Map(value) {
|
||||
if e, ok := m.data[k]; !ok {
|
||||
m.data[k] = m.list.PushBack(&gListMapNode{k, v})
|
||||
@ -597,16 +325,8 @@ func (m *ListMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[any]any, len(m.data))
|
||||
if m.list != nil {
|
||||
var node *gListMapNode
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
data[node.key] = deepcopy.Copy(node.value)
|
||||
return true
|
||||
})
|
||||
m.lazyInit()
|
||||
return &ListMap{
|
||||
ListKVMap: m.ListKVMap.DeepCopy().(*ListKVMap[any, any]),
|
||||
}
|
||||
return NewListMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
32
container/gmap/gmap_tree_k_v_map.go
Normal file
32
container/gmap/gmap_tree_k_v_map.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
//go:build go1.24
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/container/gtree"
|
||||
)
|
||||
|
||||
// TreeKVMap based on red-black tree, alias of RedBlackKVTree.
|
||||
type TreeKVMap[K comparable, V any] = gtree.RedBlackKVTree[K, V]
|
||||
|
||||
// NewTreeKVMap instantiates a tree map with the custom comparator.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewTreeKVMap[K comparable, V any](comparator func(v1, v2 K) int, safe ...bool) *TreeKVMap[K, V] {
|
||||
return gtree.NewRedBlackKVTree[K, V](comparator, safe...)
|
||||
}
|
||||
|
||||
// NewTreeKVMapFrom instantiates a tree map with the custom comparator and `data` map.
|
||||
// Note that, the param `data` map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewTreeKVMapFrom[K comparable, V any](comparator func(v1, v2 K) int, data map[K]V, safe ...bool) *TreeKVMap[K, V] {
|
||||
return gtree.NewRedBlackKVTreeFrom(comparator, data, safe...)
|
||||
}
|
||||
@ -443,3 +443,49 @@ func Test_AnyAnyMap_Diff(t *testing.T) {
|
||||
t.Assert(updatedKeys, []any{3})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AnyAnyMap_DoSetWithLockCheck_FuncValue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewAnyAnyMap(true)
|
||||
|
||||
// Test GetOrSetFuncLock with function value
|
||||
// Function should be executed and its return value should be set
|
||||
callCount := 0
|
||||
result := m.GetOrSetFuncLock(1, func() any {
|
||||
callCount++
|
||||
return "value1"
|
||||
})
|
||||
t.Assert(result, "value1")
|
||||
t.Assert(callCount, 1)
|
||||
t.Assert(m.Get(1), "value1")
|
||||
|
||||
// Test GetOrSetFuncLock again with same key
|
||||
// Function should NOT be called since key exists
|
||||
result = m.GetOrSetFuncLock(1, func() any {
|
||||
callCount++
|
||||
return "value2"
|
||||
})
|
||||
t.Assert(result, "value1")
|
||||
t.Assert(callCount, 1) // Should still be 1, function not called
|
||||
|
||||
// Test SetIfNotExistFuncLock with function value
|
||||
callCount = 0
|
||||
ok := m.SetIfNotExistFuncLock(2, func() any {
|
||||
callCount++
|
||||
return "value2"
|
||||
})
|
||||
t.Assert(ok, true)
|
||||
t.Assert(callCount, 1)
|
||||
t.Assert(m.Get(2), "value2")
|
||||
|
||||
// Test SetIfNotExistFuncLock again with same key
|
||||
// Function should NOT be called since key exists
|
||||
ok = m.SetIfNotExistFuncLock(2, func() any {
|
||||
callCount++
|
||||
return "value3"
|
||||
})
|
||||
t.Assert(ok, false)
|
||||
t.Assert(callCount, 1) // Should still be 1, function not called
|
||||
t.Assert(m.Get(2), "value2") // Value should not change
|
||||
})
|
||||
}
|
||||
|
||||
@ -96,6 +96,42 @@ func Test_StrAnyMap_Set_Fun(t *testing.T) {
|
||||
|
||||
t.Assert(m.SetIfNotExistFuncLock("b", getAny), false)
|
||||
t.Assert(m.SetIfNotExistFuncLock("d", getAny), true)
|
||||
|
||||
type T struct {
|
||||
A int
|
||||
}
|
||||
|
||||
av := m.GetOrSetFunc("s1", func() any {
|
||||
return &T{
|
||||
A: 1,
|
||||
}
|
||||
})
|
||||
ta, ok := av.(*T)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(ta.A, 1)
|
||||
|
||||
av = m.GetOrSetFunc("s1", func() any {
|
||||
return &T{
|
||||
A: 2,
|
||||
}
|
||||
})
|
||||
ta, ok = av.(*T)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(ta.A, 1)
|
||||
|
||||
av = m.GetOrSet("s1", &T{
|
||||
A: 3,
|
||||
})
|
||||
ta, ok = av.(*T)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(ta.A, 1)
|
||||
|
||||
av = m.GetOrSet("s2", &T{
|
||||
A: 4,
|
||||
})
|
||||
ta, ok = av.(*T)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(ta.A, 4)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
1632
container/gmap/gmap_z_unit_k_v_map_test.go
Normal file
1632
container/gmap/gmap_z_unit_k_v_map_test.go
Normal file
File diff suppressed because it is too large
Load Diff
326
container/gmap/gmap_z_unit_list_k_v_map_race_test.go
Normal file
326
container/gmap/gmap_z_unit_list_k_v_map_race_test.go
Normal file
@ -0,0 +1,326 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLock_Race tests the atomicity of GetOrSetFuncLock.
|
||||
// This test ensures that the callback function is only executed once even under
|
||||
// high concurrency, which verifies that the function holds the lock during the
|
||||
// entire check-and-set operation.
|
||||
func Test_ListKVMap_GetOrSetFuncLock_Race(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "counter"
|
||||
callCount := int32(0)
|
||||
goroutines := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
// Start multiple goroutines trying to set the same key
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
m.GetOrSetFuncLock(key, func() int {
|
||||
// Increment call count atomically
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
// Simulate some work
|
||||
time.Sleep(time.Microsecond)
|
||||
return 100
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// The callback should only be called once because of proper locking
|
||||
t.Assert(atomic.LoadInt32(&callCount), 1)
|
||||
t.Assert(m.Get(key), 100)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLock_Race tests the atomicity of SetIfNotExistFuncLock.
|
||||
// This test ensures that only one goroutine can successfully set the value and
|
||||
// execute the callback function, even under high concurrency.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLock_Race(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "counter"
|
||||
callCount := int32(0)
|
||||
successCount := int32(0)
|
||||
goroutines := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
// Start multiple goroutines trying to set the same key
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
success := m.SetIfNotExistFuncLock(key, func() int {
|
||||
// Increment call count atomically
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
// Simulate some work
|
||||
time.Sleep(time.Microsecond)
|
||||
return 200
|
||||
})
|
||||
if success {
|
||||
atomic.AddInt32(&successCount, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// The callback should only be called once
|
||||
t.Assert(atomic.LoadInt32(&callCount), 1)
|
||||
// Only one goroutine should succeed
|
||||
t.Assert(atomic.LoadInt32(&successCount), 1)
|
||||
t.Assert(m.Get(key), 200)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLock_MultipleKeys tests GetOrSetFuncLock with different keys.
|
||||
// This ensures that operations on different keys don't interfere with each other.
|
||||
func Test_ListKVMap_GetOrSetFuncLock_MultipleKeys(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
keys := []string{"key1", "key2", "key3", "key4", "key5"}
|
||||
callCounts := make([]int32, len(keys))
|
||||
goroutines := 20
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// For each key, start multiple goroutines
|
||||
for i, key := range keys {
|
||||
keyIndex := i
|
||||
for j := 0; j < goroutines; j++ {
|
||||
wg.Add(1)
|
||||
go func(idx int, k string) {
|
||||
defer wg.Done()
|
||||
m.GetOrSetFuncLock(k, func() int {
|
||||
atomic.AddInt32(&callCounts[idx], 1)
|
||||
time.Sleep(time.Microsecond)
|
||||
return (idx + 1) * 100
|
||||
})
|
||||
}(keyIndex, key)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Each key's callback should only be called once
|
||||
for _, count := range callCounts {
|
||||
t.Assert(atomic.LoadInt32(&count), 1)
|
||||
}
|
||||
|
||||
// Verify all keys are set correctly
|
||||
for i, key := range keys {
|
||||
t.Assert(m.Get(key), (i+1)*100)
|
||||
}
|
||||
t.Assert(m.Size(), len(keys))
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLock_MultipleKeys tests SetIfNotExistFuncLock with different keys.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLock_MultipleKeys(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[int, string](true)
|
||||
keys := []int{1, 2, 3, 4, 5}
|
||||
callCounts := make([]int32, len(keys))
|
||||
successCounts := make([]int32, len(keys))
|
||||
goroutines := 20
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// For each key, start multiple goroutines
|
||||
for i, key := range keys {
|
||||
keyIndex := i
|
||||
for j := 0; j < goroutines; j++ {
|
||||
wg.Add(1)
|
||||
go func(idx int, k int) {
|
||||
defer wg.Done()
|
||||
success := m.SetIfNotExistFuncLock(k, func() string {
|
||||
atomic.AddInt32(&callCounts[idx], 1)
|
||||
time.Sleep(time.Microsecond)
|
||||
return gtest.DataContent()
|
||||
})
|
||||
if success {
|
||||
atomic.AddInt32(&successCounts[idx], 1)
|
||||
}
|
||||
}(keyIndex, key)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Each key's callback should only be called once
|
||||
for _, count := range callCounts {
|
||||
t.Assert(atomic.LoadInt32(&count), 1)
|
||||
}
|
||||
|
||||
// Each key should have exactly one successful set
|
||||
for _, count := range successCounts {
|
||||
t.Assert(atomic.LoadInt32(&count), 1)
|
||||
}
|
||||
|
||||
t.Assert(m.Size(), len(keys))
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLock_NilValue tests that nil values are handled correctly.
|
||||
func Test_ListKVMap_GetOrSetFuncLock_NilValue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, *int](true)
|
||||
key := "nilKey"
|
||||
callCount := int32(0)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
goroutines := 50
|
||||
wg.Add(goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
m.GetOrSetFuncLock(key, func() *int {
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Callback should be called once
|
||||
t.Assert(atomic.LoadInt32(&callCount), 1)
|
||||
// Typed nil pointer (*int)(nil) is stored because any(value) != nil for typed nil
|
||||
// This is a Go language feature: typed nil is not the same as interface nil
|
||||
t.Assert(m.Contains(key), true)
|
||||
t.Assert(m.Get(key), (*int)(nil))
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLock_NilValue tests that nil values are handled correctly.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLock_NilValue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, *string](true)
|
||||
key := "nilKey"
|
||||
callCount := int32(0)
|
||||
successCount := int32(0)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
goroutines := 50
|
||||
wg.Add(goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
success := m.SetIfNotExistFuncLock(key, func() *string {
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
return nil
|
||||
})
|
||||
if success {
|
||||
atomic.AddInt32(&successCount, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Callback should be called once
|
||||
t.Assert(atomic.LoadInt32(&callCount), 1)
|
||||
// Should report success once
|
||||
t.Assert(atomic.LoadInt32(&successCount), 1)
|
||||
// Typed nil pointer (*string)(nil) is stored because any(value) != nil for typed nil
|
||||
t.Assert(m.Contains(key), true)
|
||||
t.Assert(m.Get(key), (*string)(nil))
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLock_ExistingKey tests behavior when key already exists.
|
||||
func Test_ListKVMap_GetOrSetFuncLock_ExistingKey(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "existing"
|
||||
m.Set(key, 999)
|
||||
|
||||
callCount := int32(0)
|
||||
goroutines := 50
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
val := m.GetOrSetFuncLock(key, func() int {
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
return 123
|
||||
})
|
||||
// Should always get the existing value
|
||||
t.Assert(val, 999)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Callback should never be called since key exists
|
||||
t.Assert(atomic.LoadInt32(&callCount), 0)
|
||||
t.Assert(m.Get(key), 999)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLock_ExistingKey tests behavior when key already exists.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLock_ExistingKey(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "existing"
|
||||
m.Set(key, 888)
|
||||
|
||||
callCount := int32(0)
|
||||
successCount := int32(0)
|
||||
goroutines := 50
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
success := m.SetIfNotExistFuncLock(key, func() int {
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
return 456
|
||||
})
|
||||
if success {
|
||||
atomic.AddInt32(&successCount, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Callback should never be called since key exists
|
||||
t.Assert(atomic.LoadInt32(&callCount), 0)
|
||||
// No goroutine should succeed
|
||||
t.Assert(atomic.LoadInt32(&successCount), 0)
|
||||
// Original value should remain
|
||||
t.Assert(m.Get(key), 888)
|
||||
})
|
||||
}
|
||||
1343
container/gmap/gmap_z_unit_list_k_v_map_test.go
Normal file
1343
container/gmap/gmap_z_unit_list_k_v_map_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,41 +8,19 @@
|
||||
package gpool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/glist"
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
)
|
||||
|
||||
// Pool is an Object-Reusable Pool.
|
||||
type Pool struct {
|
||||
list *glist.List // Available/idle items list.
|
||||
closed *gtype.Bool // Whether the pool is closed.
|
||||
TTL time.Duration // Time To Live for pool items.
|
||||
NewFunc func() (any, error) // Callback function to create pool item.
|
||||
// ExpireFunc is the function for expired items destruction.
|
||||
// This function needs to be defined when the pool items
|
||||
// need to perform additional destruction operations.
|
||||
// Eg: net.Conn, os.File, etc.
|
||||
ExpireFunc func(any)
|
||||
}
|
||||
|
||||
// Pool item.
|
||||
type poolItem struct {
|
||||
value any // Item value.
|
||||
expireAt int64 // Expire timestamp in milliseconds.
|
||||
*TPool[any]
|
||||
}
|
||||
|
||||
// NewFunc Creation function for object.
|
||||
type NewFunc func() (any, error)
|
||||
type NewFunc = TPoolNewFunc[any]
|
||||
|
||||
// ExpireFunc Destruction function for object.
|
||||
type ExpireFunc func(any)
|
||||
type ExpireFunc = TPoolExpireFunc[any]
|
||||
|
||||
// New creates and returns a new object pool.
|
||||
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
|
||||
@ -52,134 +30,40 @@ type ExpireFunc func(any)
|
||||
// ttl < 0 : immediate expired after use;
|
||||
// ttl > 0 : timeout expired;
|
||||
func New(ttl time.Duration, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
|
||||
r := &Pool{
|
||||
list: glist.New(true),
|
||||
closed: gtype.NewBool(),
|
||||
TTL: ttl,
|
||||
NewFunc: newFunc,
|
||||
return &Pool{
|
||||
TPool: NewTPool(ttl, newFunc, expireFunc...),
|
||||
}
|
||||
if len(expireFunc) > 0 {
|
||||
r.ExpireFunc = expireFunc[0]
|
||||
}
|
||||
gtimer.AddSingleton(context.Background(), time.Second, r.checkExpireItems)
|
||||
return r
|
||||
}
|
||||
|
||||
// Put puts an item to pool.
|
||||
func (p *Pool) Put(value any) error {
|
||||
if p.closed.Val() {
|
||||
return gerror.NewCode(gcode.CodeInvalidOperation, "pool is closed")
|
||||
}
|
||||
item := &poolItem{
|
||||
value: value,
|
||||
}
|
||||
if p.TTL == 0 {
|
||||
item.expireAt = 0
|
||||
} else {
|
||||
// As for Golang version < 1.13, there's no method Milliseconds for time.Duration.
|
||||
// So we need calculate the milliseconds using its nanoseconds value.
|
||||
item.expireAt = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
|
||||
}
|
||||
p.list.PushBack(item)
|
||||
return nil
|
||||
return p.TPool.Put(value)
|
||||
}
|
||||
|
||||
// MustPut puts an item to pool, it panics if any error occurs.
|
||||
func (p *Pool) MustPut(value any) {
|
||||
if err := p.Put(value); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.TPool.MustPut(value)
|
||||
}
|
||||
|
||||
// Clear clears pool, which means it will remove all items from pool.
|
||||
func (p *Pool) Clear() {
|
||||
if p.ExpireFunc != nil {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
p.ExpireFunc(r.(*poolItem).value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p.list.RemoveAll()
|
||||
}
|
||||
p.TPool.Clear()
|
||||
}
|
||||
|
||||
// Get picks and returns an item from pool. If the pool is empty and NewFunc is defined,
|
||||
// it creates and returns one from NewFunc.
|
||||
func (p *Pool) Get() (any, error) {
|
||||
for !p.closed.Val() {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
f := r.(*poolItem)
|
||||
if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
|
||||
return f.value, nil
|
||||
} else if p.ExpireFunc != nil {
|
||||
// TODO: move expire function calling asynchronously out from `Get` operation.
|
||||
p.ExpireFunc(f.value)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.NewFunc != nil {
|
||||
return p.NewFunc()
|
||||
}
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidOperation, "pool is empty")
|
||||
return p.TPool.Get()
|
||||
}
|
||||
|
||||
// Size returns the count of available items of pool.
|
||||
func (p *Pool) Size() int {
|
||||
return p.list.Len()
|
||||
return p.TPool.Size()
|
||||
}
|
||||
|
||||
// Close closes the pool. If `p` has ExpireFunc,
|
||||
// then it automatically closes all items using this function before it's closed.
|
||||
// Commonly you do not need to call this function manually.
|
||||
func (p *Pool) Close() {
|
||||
p.closed.Set(true)
|
||||
}
|
||||
|
||||
// checkExpire removes expired items from pool in every second.
|
||||
func (p *Pool) checkExpireItems(ctx context.Context) {
|
||||
if p.closed.Val() {
|
||||
// If p has ExpireFunc,
|
||||
// then it must close all items using this function.
|
||||
if p.ExpireFunc != nil {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
p.ExpireFunc(r.(*poolItem).value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
gtimer.Exit()
|
||||
}
|
||||
// All items do not expire.
|
||||
if p.TTL == 0 {
|
||||
return
|
||||
}
|
||||
// The latest item expire timestamp in milliseconds.
|
||||
var latestExpire int64 = -1
|
||||
// Retrieve the current timestamp in milliseconds, it expires the items
|
||||
// by comparing with this timestamp. It is not accurate comparison for
|
||||
// every item expired, but high performance.
|
||||
var timestampMilli = gtime.TimestampMilli()
|
||||
for latestExpire <= timestampMilli {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
item := r.(*poolItem)
|
||||
latestExpire = item.expireAt
|
||||
// TODO improve the auto-expiration mechanism of the pool.
|
||||
if item.expireAt > timestampMilli {
|
||||
p.list.PushFront(item)
|
||||
break
|
||||
}
|
||||
if p.ExpireFunc != nil {
|
||||
p.ExpireFunc(item.value)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
p.TPool.Close()
|
||||
}
|
||||
|
||||
183
container/gpool/gpool_t.go
Normal file
183
container/gpool/gpool_t.go
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gpool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/glist"
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
)
|
||||
|
||||
// TPool is an Object-Reusable Pool.
|
||||
type TPool[T any] struct {
|
||||
list *glist.TList[*tPoolItem[T]] // Available/idle items list.
|
||||
closed *gtype.Bool // Whether the pool is closed.
|
||||
TTL time.Duration // Time To Live for pool items.
|
||||
NewFunc func() (T, error) // Callback function to create pool item.
|
||||
// ExpireFunc is the function for expired items destruction.
|
||||
// This function needs to be defined when the pool items
|
||||
// need to perform additional destruction operations.
|
||||
// Eg: net.Conn, os.File, etc.
|
||||
ExpireFunc func(T)
|
||||
}
|
||||
|
||||
// TPool item.
|
||||
type tPoolItem[T any] struct {
|
||||
value T // Item value.
|
||||
expireAt int64 // Expire timestamp in milliseconds.
|
||||
}
|
||||
|
||||
// TPoolNewFunc Creation function for object.
|
||||
type TPoolNewFunc[T any] func() (T, error)
|
||||
|
||||
// TPoolExpireFunc Destruction function for object.
|
||||
type TPoolExpireFunc[T any] func(T)
|
||||
|
||||
// NewTPool creates and returns a new object pool.
|
||||
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
|
||||
//
|
||||
// Note the expiration logic:
|
||||
// ttl = 0 : not expired;
|
||||
// ttl < 0 : immediate expired after use;
|
||||
// ttl > 0 : timeout expired;
|
||||
func NewTPool[T any](ttl time.Duration, newFunc TPoolNewFunc[T], expireFunc ...TPoolExpireFunc[T]) *TPool[T] {
|
||||
r := &TPool[T]{
|
||||
list: glist.NewT[*tPoolItem[T]](true),
|
||||
closed: gtype.NewBool(),
|
||||
TTL: ttl,
|
||||
NewFunc: newFunc,
|
||||
}
|
||||
if len(expireFunc) > 0 {
|
||||
r.ExpireFunc = expireFunc[0]
|
||||
}
|
||||
gtimer.AddSingleton(context.Background(), time.Second, r.checkExpireItems)
|
||||
return r
|
||||
}
|
||||
|
||||
// Put puts an item to pool.
|
||||
func (p *TPool[T]) Put(value T) error {
|
||||
if p.closed.Val() {
|
||||
return gerror.NewCode(gcode.CodeInvalidOperation, "pool is closed")
|
||||
}
|
||||
item := &tPoolItem[T]{
|
||||
value: value,
|
||||
}
|
||||
if p.TTL == 0 {
|
||||
item.expireAt = 0
|
||||
} else {
|
||||
// As for Golang version < 1.13, there's no method Milliseconds for time.Duration.
|
||||
// So we need calculate the milliseconds using its nanoseconds value.
|
||||
item.expireAt = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
|
||||
}
|
||||
p.list.PushBack(item)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustPut puts an item to pool, it panics if any error occurs.
|
||||
func (p *TPool[T]) MustPut(value T) {
|
||||
if err := p.Put(value); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear clears pool, which means it will remove all items from pool.
|
||||
func (p *TPool[T]) Clear() {
|
||||
if p.ExpireFunc != nil {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
p.ExpireFunc(r.value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p.list.RemoveAll()
|
||||
}
|
||||
}
|
||||
|
||||
// Get picks and returns an item from pool. If the pool is empty and NewFunc is defined,
|
||||
// it creates and returns one from NewFunc.
|
||||
func (p *TPool[T]) Get() (value T, err error) {
|
||||
for !p.closed.Val() {
|
||||
if f := p.list.PopFront(); f != nil {
|
||||
if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
|
||||
return f.value, nil
|
||||
} else if p.ExpireFunc != nil {
|
||||
// TODO: move expire function calling asynchronously out from `Get` operation.
|
||||
p.ExpireFunc(f.value)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.NewFunc != nil {
|
||||
return p.NewFunc()
|
||||
}
|
||||
err = gerror.NewCode(gcode.CodeInvalidOperation, "pool is empty")
|
||||
return
|
||||
}
|
||||
|
||||
// Size returns the count of available items of pool.
|
||||
func (p *TPool[T]) Size() int {
|
||||
return p.list.Len()
|
||||
}
|
||||
|
||||
// Close closes the pool. If `p` has ExpireFunc,
|
||||
// then it automatically closes all items using this function before it's closed.
|
||||
// Commonly you do not need to call this function manually.
|
||||
func (p *TPool[T]) Close() {
|
||||
p.closed.Set(true)
|
||||
}
|
||||
|
||||
// checkExpire removes expired items from pool in every second.
|
||||
func (p *TPool[T]) checkExpireItems(ctx context.Context) {
|
||||
if p.closed.Val() {
|
||||
// If p has ExpireFunc,
|
||||
// then it must close all items using this function.
|
||||
if p.ExpireFunc != nil {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
p.ExpireFunc(r.value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
gtimer.Exit()
|
||||
}
|
||||
// All items do not expire.
|
||||
if p.TTL == 0 {
|
||||
return
|
||||
}
|
||||
// The latest item expire timestamp in milliseconds.
|
||||
var latestExpire int64 = -1
|
||||
// Retrieve the current timestamp in milliseconds, it expires the items
|
||||
// by comparing with this timestamp. It is not accurate comparison for
|
||||
// every item expired, but high performance.
|
||||
var timestampMilli = gtime.TimestampMilli()
|
||||
for latestExpire <= timestampMilli {
|
||||
if item := p.list.PopFront(); item != nil {
|
||||
latestExpire = item.expireAt
|
||||
// TODO improve the auto-expiration mechanism of the pool.
|
||||
if item.expireAt > timestampMilli {
|
||||
p.list.PushFront(item)
|
||||
break
|
||||
}
|
||||
if p.ExpireFunc != nil {
|
||||
p.ExpireFunc(item.value)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
112
container/gpool/gpool_z_unit_generic_test.go
Normal file
112
container/gpool/gpool_z_unit_generic_test.go
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gpool_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gpool"
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func Test_TPool_Int(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a pool for int
|
||||
var (
|
||||
newFunc = func() (int, error) {
|
||||
return 100, nil
|
||||
}
|
||||
expireVal = gtype.NewInt(0)
|
||||
expireFunc = func(i int) {
|
||||
expireVal.Set(i)
|
||||
}
|
||||
)
|
||||
|
||||
// TTL = 0, no expiration by time
|
||||
p := gpool.NewTPool(0, newFunc, expireFunc)
|
||||
|
||||
// Test Put and Get
|
||||
p.Put(1)
|
||||
p.Put(2)
|
||||
t.Assert(p.Size(), 2)
|
||||
|
||||
v, err := p.Get()
|
||||
t.AssertNil(err)
|
||||
t.AssertIN(v, g.Slice{1, 2})
|
||||
|
||||
v, err = p.Get()
|
||||
t.AssertNil(err)
|
||||
t.AssertIN(v, g.Slice{1, 2})
|
||||
|
||||
t.Assert(p.Size(), 0)
|
||||
|
||||
// Test NewFunc when empty
|
||||
v, err = p.Get()
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, 100)
|
||||
|
||||
// Test Clear and ExpireFunc
|
||||
p.Put(50)
|
||||
t.Assert(p.Size(), 1)
|
||||
p.Clear()
|
||||
t.Assert(p.Size(), 0)
|
||||
t.Assert(expireVal.Val(), 50)
|
||||
|
||||
// Test Close
|
||||
p.Put(60)
|
||||
p.Close()
|
||||
// Close should trigger expire for existing items?
|
||||
// Looking at implementation: Close() sets closed=true.
|
||||
// It does NOT automatically clear items unless checkExpireItems runs or we call Clear?
|
||||
// Wait, checkExpireItems checks closed.Val(). If closed, it clears items.
|
||||
// But checkExpireItems runs in a separate goroutine every second.
|
||||
// So we might need to wait or trigger it.
|
||||
// Actually, let's check the implementation of Close again.
|
||||
/*
|
||||
func (p *TPool[T]) Close() {
|
||||
p.closed.Set(true)
|
||||
}
|
||||
*/
|
||||
// And checkExpireItems:
|
||||
/*
|
||||
func (p *TPool[T]) checkExpireItems(ctx context.Context) {
|
||||
if p.closed.Val() {
|
||||
// ... clears items ...
|
||||
gtimer.Exit()
|
||||
}
|
||||
// ...
|
||||
}
|
||||
*/
|
||||
// So it relies on the timer to clean up.
|
||||
})
|
||||
}
|
||||
|
||||
func Test_TPool_Struct(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
p := gpool.NewTPool[User](time.Hour, nil)
|
||||
u1 := User{Id: 1, Name: "john"}
|
||||
p.Put(u1)
|
||||
|
||||
v, err := p.Get()
|
||||
t.AssertNil(err)
|
||||
t.Assert(v, u1)
|
||||
|
||||
// Test empty with no NewFunc
|
||||
v, err = p.Get()
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Error(), "pool is empty")
|
||||
t.Assert(v, User{}) // Zero value
|
||||
})
|
||||
}
|
||||
@ -77,7 +77,7 @@ func Test_Gpool(t *testing.T) {
|
||||
t.Assert(err2, errors.New("pool is empty"))
|
||||
t.Assert(v2, nil)
|
||||
// test close expireFunc
|
||||
for index := 0; index < 10; index++ {
|
||||
for index := range 10 {
|
||||
p2.Put(index)
|
||||
}
|
||||
t.Assert(p2.Size(), 10)
|
||||
|
||||
@ -17,20 +17,9 @@
|
||||
// 4. Blocking when reading data from queue;
|
||||
package gqueue
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/gogf/gf/v2/container/glist"
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
)
|
||||
|
||||
// Queue is a concurrent-safe queue built on doubly linked list and channel.
|
||||
type Queue struct {
|
||||
limit int // Limit for queue size.
|
||||
list *glist.List // Underlying list structure for data maintaining.
|
||||
closed *gtype.Bool // Whether queue is closed.
|
||||
events chan struct{} // Events for data writing.
|
||||
C chan any // Underlying channel for data reading.
|
||||
*TQueue[any]
|
||||
}
|
||||
|
||||
const (
|
||||
@ -42,74 +31,35 @@ const (
|
||||
// Optional parameter `limit` is used to limit the size of the queue, which is unlimited in default.
|
||||
// When `limit` is given, the queue will be static and high performance which is comparable with stdlib channel.
|
||||
func New(limit ...int) *Queue {
|
||||
q := &Queue{
|
||||
closed: gtype.NewBool(),
|
||||
return &Queue{
|
||||
TQueue: NewTQueue[any](limit...),
|
||||
}
|
||||
if len(limit) > 0 && limit[0] > 0 {
|
||||
q.limit = limit[0]
|
||||
q.C = make(chan any, limit[0])
|
||||
} else {
|
||||
q.list = glist.New(true)
|
||||
q.events = make(chan struct{}, math.MaxInt32)
|
||||
q.C = make(chan any, defaultQueueSize)
|
||||
go q.asyncLoopFromListToChannel()
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// Push pushes the data `v` into the queue.
|
||||
// Note that it would panic if Push is called after the queue is closed.
|
||||
func (q *Queue) Push(v any) {
|
||||
if q.limit > 0 {
|
||||
q.C <- v
|
||||
} else {
|
||||
q.list.PushBack(v)
|
||||
if len(q.events) < defaultQueueSize {
|
||||
q.events <- struct{}{}
|
||||
}
|
||||
}
|
||||
q.TQueue.Push(v)
|
||||
}
|
||||
|
||||
// Pop pops an item from the queue in FIFO way.
|
||||
// Note that it would return nil immediately if Pop is called after the queue is closed.
|
||||
func (q *Queue) Pop() any {
|
||||
return <-q.C
|
||||
return q.TQueue.Pop()
|
||||
}
|
||||
|
||||
// Close closes the queue.
|
||||
// Notice: It would notify all goroutines return immediately,
|
||||
// which are being blocked reading using Pop method.
|
||||
func (q *Queue) Close() {
|
||||
if !q.closed.Cas(false, true) {
|
||||
return
|
||||
}
|
||||
if q.events != nil {
|
||||
close(q.events)
|
||||
}
|
||||
if q.limit > 0 {
|
||||
close(q.C)
|
||||
} else {
|
||||
for range defaultBatchSize {
|
||||
q.Pop()
|
||||
}
|
||||
}
|
||||
q.TQueue.Close()
|
||||
}
|
||||
|
||||
// Len returns the length of the queue.
|
||||
// Note that the result might not be accurate if using unlimited queue size as there's an
|
||||
// asynchronous channel reading the list constantly.
|
||||
func (q *Queue) Len() (length int64) {
|
||||
bufferedSize := int64(len(q.C))
|
||||
if q.limit > 0 {
|
||||
return bufferedSize
|
||||
}
|
||||
// If the queue is unlimited and the buffered size is exactly the default size,
|
||||
// it means there might be some data in the list not synchronized to channel yet.
|
||||
// So we need to add 1 to the buffered size to make the result more accurate.
|
||||
if bufferedSize == defaultQueueSize {
|
||||
bufferedSize++
|
||||
}
|
||||
return int64(q.list.Size()) + bufferedSize
|
||||
return q.TQueue.Len()
|
||||
}
|
||||
|
||||
// Size is alias of Len.
|
||||
@ -118,34 +68,3 @@ func (q *Queue) Len() (length int64) {
|
||||
func (q *Queue) Size() int64 {
|
||||
return q.Len()
|
||||
}
|
||||
|
||||
// asyncLoopFromListToChannel starts an asynchronous goroutine,
|
||||
// which handles the data synchronization from list `q.list` to channel `q.C`.
|
||||
func (q *Queue) asyncLoopFromListToChannel() {
|
||||
defer func() {
|
||||
if q.closed.Val() {
|
||||
_ = recover()
|
||||
}
|
||||
}()
|
||||
for !q.closed.Val() {
|
||||
<-q.events
|
||||
for !q.closed.Val() {
|
||||
if bufferLength := q.list.Len(); bufferLength > 0 {
|
||||
// When q.C is closed, it will panic here, especially q.C is being blocked for writing.
|
||||
// If any error occurs here, it will be caught by recover and be ignored.
|
||||
for range bufferLength {
|
||||
q.C <- q.list.PopFront()
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Clear q.events to remain just one event to do the next synchronization check.
|
||||
for i := 0; i < len(q.events)-1; i++ {
|
||||
<-q.events
|
||||
}
|
||||
}
|
||||
// It should be here to close `q.C` if `q` is unlimited size.
|
||||
// It's the sender's responsibility to close channel when it should be closed.
|
||||
close(q.C)
|
||||
}
|
||||
|
||||
134
container/gqueue/gqueue_t.go
Normal file
134
container/gqueue/gqueue_t.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
package gqueue
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/gogf/gf/v2/container/glist"
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
)
|
||||
|
||||
// TQueue is a concurrent-safe queue built on doubly linked list and channel.
|
||||
type TQueue[T any] struct {
|
||||
limit int // Limit for queue size.
|
||||
list *glist.TList[T] // Underlying list structure for data maintaining.
|
||||
closed *gtype.Bool // Whether queue is closed.
|
||||
events chan struct{} // Events for data writing.
|
||||
C chan T // Underlying channel for data reading.
|
||||
}
|
||||
|
||||
// NewTQueue returns an empty queue object.
|
||||
// Optional parameter `limit` is used to limit the size of the queue, which is unlimited in default.
|
||||
// When `limit` is given, the queue will be static and high performance which is comparable with stdlib channel.
|
||||
func NewTQueue[T any](limit ...int) *TQueue[T] {
|
||||
q := &TQueue[T]{
|
||||
closed: gtype.NewBool(),
|
||||
}
|
||||
if len(limit) > 0 && limit[0] > 0 {
|
||||
q.limit = limit[0]
|
||||
q.C = make(chan T, limit[0])
|
||||
} else {
|
||||
q.list = glist.NewT[T](true)
|
||||
q.events = make(chan struct{}, math.MaxInt32)
|
||||
q.C = make(chan T, defaultQueueSize)
|
||||
go q.asyncLoopFromListToChannel()
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// Push pushes the data `v` into the queue.
|
||||
// Note that it would panic if Push is called after the queue is closed.
|
||||
func (q *TQueue[T]) Push(v T) {
|
||||
if q.limit > 0 {
|
||||
q.C <- v
|
||||
} else {
|
||||
q.list.PushBack(v)
|
||||
if len(q.events) < defaultQueueSize {
|
||||
q.events <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop pops an item from the queue in FIFO way.
|
||||
// Note that it would return nil immediately if Pop is called after the queue is closed.
|
||||
func (q *TQueue[T]) Pop() T {
|
||||
return <-q.C
|
||||
}
|
||||
|
||||
// Close closes the queue.
|
||||
// Notice: It would notify all goroutines return immediately,
|
||||
// which are being blocked reading using Pop method.
|
||||
func (q *TQueue[T]) Close() {
|
||||
if !q.closed.Cas(false, true) {
|
||||
return
|
||||
}
|
||||
if q.events != nil {
|
||||
close(q.events)
|
||||
}
|
||||
if q.limit > 0 {
|
||||
close(q.C)
|
||||
} else {
|
||||
for range defaultBatchSize {
|
||||
q.Pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the length of the queue.
|
||||
// Note that the result might not be accurate if using unlimited queue size as there's an
|
||||
// asynchronous channel reading the list constantly.
|
||||
func (q *TQueue[T]) Len() (length int64) {
|
||||
bufferedSize := int64(len(q.C))
|
||||
if q.limit > 0 {
|
||||
return bufferedSize
|
||||
}
|
||||
// If the queue is unlimited and the buffered size is exactly the default size,
|
||||
// it means there might be some data in the list not synchronized to channel yet.
|
||||
// So we need to add 1 to the buffered size to make the result more accurate.
|
||||
if bufferedSize == defaultQueueSize {
|
||||
bufferedSize++
|
||||
}
|
||||
return int64(q.list.Size()) + bufferedSize
|
||||
}
|
||||
|
||||
// Size is alias of Len.
|
||||
//
|
||||
// Deprecated: use Len instead.
|
||||
func (q *TQueue[T]) Size() int64 {
|
||||
return q.Len()
|
||||
}
|
||||
|
||||
// asyncLoopFromListToChannel starts an asynchronous goroutine,
|
||||
// which handles the data synchronization from list `q.list` to channel `q.C`.
|
||||
func (q *TQueue[T]) asyncLoopFromListToChannel() {
|
||||
defer func() {
|
||||
if q.closed.Val() {
|
||||
_ = recover()
|
||||
}
|
||||
}()
|
||||
for !q.closed.Val() {
|
||||
<-q.events
|
||||
for !q.closed.Val() {
|
||||
if bufferLength := q.list.Len(); bufferLength > 0 {
|
||||
// When q.C is closed, it will panic here, especially q.C is being blocked for writing.
|
||||
// If any error occurs here, it will be caught by recover and be ignored.
|
||||
for range bufferLength {
|
||||
q.C <- q.list.PopFront()
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Clear q.events to remain just one event to do the next synchronization check.
|
||||
for i := 0; i < len(q.events)-1; i++ {
|
||||
<-q.events
|
||||
}
|
||||
}
|
||||
// It should be here to close `q.C` if `q` is unlimited size.
|
||||
// It's the sender's responsibility to close channel when it should be closed.
|
||||
close(q.C)
|
||||
}
|
||||
@ -128,3 +128,218 @@ func TestIssue4376(t *testing.T) {
|
||||
t.Log(gq.Len(), len(cq))
|
||||
})
|
||||
}
|
||||
|
||||
// Test static queue (with limit) close operation
|
||||
func TestQueue_StaticClose(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New(10)
|
||||
defer func() {
|
||||
if err := recover(); err == nil {
|
||||
t.Log("Close succeeded")
|
||||
}
|
||||
}()
|
||||
q.Push(1)
|
||||
q.Push(2)
|
||||
q.Close()
|
||||
// After closing, Pop should return nil
|
||||
v := q.Pop()
|
||||
t.Assert(v, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test Size() method (deprecated alias of Len)
|
||||
func TestQueue_Size(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New(20)
|
||||
for i := range 10 {
|
||||
q.Push(i)
|
||||
}
|
||||
t.Assert(q.Size(), 10)
|
||||
t.Assert(q.Len(), 10)
|
||||
q.Close()
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New()
|
||||
for i := range 15 {
|
||||
q.Push(i)
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
t.Assert(q.Size(), q.Len())
|
||||
q.Close()
|
||||
})
|
||||
}
|
||||
|
||||
// Test TQueue directly with generic type
|
||||
func TestTQueue_Generic(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test with custom type
|
||||
q := gqueue.NewTQueue[string]()
|
||||
defer q.Close()
|
||||
q.Push("hello")
|
||||
q.Push("world")
|
||||
t.Assert(q.Pop(), "hello")
|
||||
t.Assert(q.Pop(), "world")
|
||||
})
|
||||
}
|
||||
|
||||
// Test TQueue Size method directly
|
||||
func TestTQueue_Size(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.NewTQueue[int]()
|
||||
defer q.Close()
|
||||
for i := range 10 {
|
||||
q.Push(i)
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// Size is an alias of Len for TQueue
|
||||
t.Assert(q.Size(), q.Len())
|
||||
})
|
||||
}
|
||||
|
||||
// Test TQueue with static limit
|
||||
func TestTQueue_StaticLimit(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.NewTQueue[int](5)
|
||||
defer q.Close()
|
||||
for i := range 5 {
|
||||
q.Push(i)
|
||||
}
|
||||
t.Assert(q.Len(), 5)
|
||||
for i := range 5 {
|
||||
t.Assert(q.Pop(), i)
|
||||
}
|
||||
t.Assert(q.Len(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
// Test queue with large data push/pop
|
||||
func TestQueue_LargeDataScale(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New()
|
||||
defer q.Close()
|
||||
n := 5000
|
||||
for i := range n {
|
||||
q.Push(i)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
// Pop should retrieve all items in order
|
||||
for i := range n {
|
||||
v := q.Pop()
|
||||
t.Assert(v, i)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test double close (idempotent close)
|
||||
func TestQueue_DoubleClose(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New()
|
||||
q.Push(1)
|
||||
q.Close()
|
||||
// Second close should not panic
|
||||
q.Close()
|
||||
t.Assert(q.Pop(), nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New(10)
|
||||
q.Push(1)
|
||||
q.Close()
|
||||
// Second close should not panic for static queue
|
||||
q.Close()
|
||||
// Pop from closed static queue returns the buffered value
|
||||
v := q.Pop()
|
||||
t.Assert(v, 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test concurrent push and pop
|
||||
func TestQueue_ConcurrentPushPop(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New()
|
||||
defer q.Close()
|
||||
// Producer goroutine
|
||||
go func() {
|
||||
for i := range 100 {
|
||||
q.Push(i)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
q.Close()
|
||||
}()
|
||||
// Consumer
|
||||
count := 0
|
||||
for {
|
||||
v := q.Pop()
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
count++
|
||||
}
|
||||
t.AssertGE(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test Pop on empty queue returns nil when closed
|
||||
func TestQueue_PopEmptyClosed(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New()
|
||||
q.Close()
|
||||
v := q.Pop()
|
||||
t.Assert(v, nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New(10)
|
||||
q.Close()
|
||||
v := q.Pop()
|
||||
t.Assert(v, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test Len with dynamic queue at capacity boundary
|
||||
func TestQueue_LenAtBoundary(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New()
|
||||
defer q.Close()
|
||||
// Push exactly defaultQueueSize items to test boundary condition
|
||||
for i := range 10000 {
|
||||
q.Push(i)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
len := q.Len()
|
||||
t.AssertGE(len, 0)
|
||||
})
|
||||
}
|
||||
|
||||
// Test Close on dynamic queue with pending asyncLoopFromListToChannel
|
||||
func TestQueue_CloseWithAsyncLoop(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New()
|
||||
// Push some data to activate asyncLoopFromListToChannel
|
||||
for i := range 100 {
|
||||
q.Push(i)
|
||||
}
|
||||
// Immediately close
|
||||
q.Close()
|
||||
// Pop should return values until exhausted, then nil
|
||||
for {
|
||||
v := q.Pop()
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Assert(q.Pop(), nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test static queue edge case with zero limit (should create unlimited queue)
|
||||
func TestQueue_ZeroLimitCreatesUnlimited(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
q := gqueue.New(0)
|
||||
defer q.Close()
|
||||
for i := range 100 {
|
||||
q.Push(i)
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
len := q.Len()
|
||||
t.Assert(len, 100)
|
||||
})
|
||||
}
|
||||
|
||||
@ -9,27 +9,11 @@
|
||||
// Deprecated.
|
||||
package gring
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
)
|
||||
|
||||
// Ring is a struct of ring structure.
|
||||
//
|
||||
// Deprecated.
|
||||
type Ring struct {
|
||||
mu *rwmutex.RWMutex
|
||||
ring *ring.Ring // Underlying ring.
|
||||
len *gtype.Int // Length(already used size).
|
||||
cap *gtype.Int // Capability(>=len).
|
||||
dirty *gtype.Bool // Dirty, which means the len and cap should be recalculated. It's marked dirty when the size of ring changes.
|
||||
}
|
||||
|
||||
// internalRingItem stores the ring element value.
|
||||
type internalRingItem struct {
|
||||
Value any
|
||||
*TRing[any]
|
||||
}
|
||||
|
||||
// New creates and returns a Ring structure of `cap` elements.
|
||||
@ -39,108 +23,53 @@ type internalRingItem struct {
|
||||
// Deprecated.
|
||||
func New(cap int, safe ...bool) *Ring {
|
||||
return &Ring{
|
||||
mu: rwmutex.New(safe...),
|
||||
ring: ring.New(cap),
|
||||
len: gtype.NewInt(),
|
||||
cap: gtype.NewInt(cap),
|
||||
dirty: gtype.NewBool(),
|
||||
TRing: NewTRing[any](cap, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// Val returns the item's value of current position.
|
||||
func (r *Ring) Val() any {
|
||||
var value any
|
||||
r.mu.RLock()
|
||||
if r.ring.Value != nil {
|
||||
value = r.ring.Value.(internalRingItem).Value
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
return value
|
||||
return r.TRing.Val()
|
||||
}
|
||||
|
||||
// Len returns the size of ring.
|
||||
func (r *Ring) Len() int {
|
||||
r.checkAndUpdateLenAndCap()
|
||||
return r.len.Val()
|
||||
return r.TRing.Len()
|
||||
}
|
||||
|
||||
// Cap returns the capacity of ring.
|
||||
func (r *Ring) Cap() int {
|
||||
r.checkAndUpdateLenAndCap()
|
||||
return r.cap.Val()
|
||||
}
|
||||
|
||||
// Checks and updates the len and cap of ring when ring is dirty.
|
||||
func (r *Ring) checkAndUpdateLenAndCap() {
|
||||
if !r.dirty.Val() {
|
||||
return
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
totalLen := 0
|
||||
emptyLen := 0
|
||||
if r.ring != nil {
|
||||
if r.ring.Value == nil {
|
||||
emptyLen++
|
||||
}
|
||||
totalLen++
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if p.Value == nil {
|
||||
emptyLen++
|
||||
}
|
||||
totalLen++
|
||||
}
|
||||
}
|
||||
r.cap.Set(totalLen)
|
||||
r.len.Set(totalLen - emptyLen)
|
||||
r.dirty.Set(false)
|
||||
return r.TRing.Cap()
|
||||
}
|
||||
|
||||
// Set sets value to the item of current position.
|
||||
func (r *Ring) Set(value any) *Ring {
|
||||
r.mu.Lock()
|
||||
if r.ring.Value == nil {
|
||||
r.len.Add(1)
|
||||
}
|
||||
r.ring.Value = internalRingItem{Value: value}
|
||||
r.mu.Unlock()
|
||||
r.TRing.Set(value)
|
||||
return r
|
||||
}
|
||||
|
||||
// Put sets `value` to current item of ring and moves position to next item.
|
||||
func (r *Ring) Put(value any) *Ring {
|
||||
r.mu.Lock()
|
||||
if r.ring.Value == nil {
|
||||
r.len.Add(1)
|
||||
}
|
||||
r.ring.Value = internalRingItem{Value: value}
|
||||
r.ring = r.ring.Next()
|
||||
r.mu.Unlock()
|
||||
r.TRing.Put(value)
|
||||
return r
|
||||
}
|
||||
|
||||
// Move moves n % r.Len() elements backward (n < 0) or forward (n >= 0)
|
||||
// in the ring and returns that ring element. r must not be empty.
|
||||
func (r *Ring) Move(n int) *Ring {
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Move(n)
|
||||
r.mu.Unlock()
|
||||
r.TRing.Move(n)
|
||||
return r
|
||||
}
|
||||
|
||||
// Prev returns the previous ring element. r must not be empty.
|
||||
func (r *Ring) Prev() *Ring {
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Prev()
|
||||
r.mu.Unlock()
|
||||
r.TRing.Prev()
|
||||
return r
|
||||
}
|
||||
|
||||
// Next returns the next ring element. r must not be empty.
|
||||
func (r *Ring) Next() *Ring {
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Next()
|
||||
r.mu.Unlock()
|
||||
r.TRing.Next()
|
||||
return r
|
||||
}
|
||||
|
||||
@ -160,13 +89,7 @@ func (r *Ring) Next() *Ring {
|
||||
// after r. The result points to the element following the
|
||||
// last element of s after insertion.
|
||||
func (r *Ring) Link(s *Ring) *Ring {
|
||||
r.mu.Lock()
|
||||
s.mu.Lock()
|
||||
r.ring.Link(s.ring)
|
||||
s.mu.Unlock()
|
||||
r.mu.Unlock()
|
||||
r.dirty.Set(true)
|
||||
s.dirty.Set(true)
|
||||
r.TRing.Link(s.TRing)
|
||||
return r
|
||||
}
|
||||
|
||||
@ -174,78 +97,31 @@ func (r *Ring) Link(s *Ring) *Ring {
|
||||
// at r.Next(). If n % r.Len() == 0, r remains unchanged.
|
||||
// The result is the removed sub-ring. r must not be empty.
|
||||
func (r *Ring) Unlink(n int) *Ring {
|
||||
r.mu.Lock()
|
||||
resultRing := r.ring.Unlink(n)
|
||||
r.dirty.Set(true)
|
||||
r.mu.Unlock()
|
||||
resultGRing := New(resultRing.Len())
|
||||
resultGRing.ring = resultRing
|
||||
resultGRing.dirty.Set(true)
|
||||
return resultGRing
|
||||
return &Ring{
|
||||
TRing: r.TRing.Unlink(n),
|
||||
}
|
||||
}
|
||||
|
||||
// RLockIteratorNext iterates and locks reading forward
|
||||
// with given callback function `f` within RWMutex.RLock.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (r *Ring) RLockIteratorNext(f func(value any) bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if r.ring.Value != nil && !f(r.ring.Value.(internalRingItem).Value) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if p.Value == nil || !f(p.Value.(internalRingItem).Value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.TRing.RLockIteratorNext(f)
|
||||
}
|
||||
|
||||
// RLockIteratorPrev iterates and locks writing backward
|
||||
// RLockIteratorPrev iterates and locks reading backward
|
||||
// with given callback function `f` within RWMutex.RLock.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (r *Ring) RLockIteratorPrev(f func(value any) bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if r.ring.Value != nil && !f(r.ring.Value.(internalRingItem).Value) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
|
||||
if p.Value == nil || !f(p.Value.(internalRingItem).Value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.TRing.RLockIteratorPrev(f)
|
||||
}
|
||||
|
||||
// SliceNext returns a copy of all item values as slice forward from current position.
|
||||
func (r *Ring) SliceNext() []any {
|
||||
s := make([]any, 0)
|
||||
r.mu.RLock()
|
||||
if r.ring.Value != nil {
|
||||
s = append(s, r.ring.Value.(internalRingItem).Value)
|
||||
}
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if p.Value == nil {
|
||||
break
|
||||
}
|
||||
s = append(s, p.Value.(internalRingItem).Value)
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
return s
|
||||
return r.TRing.SliceNext()
|
||||
}
|
||||
|
||||
// SlicePrev returns a copy of all item values as slice backward from current position.
|
||||
func (r *Ring) SlicePrev() []any {
|
||||
s := make([]any, 0)
|
||||
r.mu.RLock()
|
||||
if r.ring.Value != nil {
|
||||
s = append(s, r.ring.Value.(internalRingItem).Value)
|
||||
}
|
||||
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
|
||||
if p.Value == nil {
|
||||
break
|
||||
}
|
||||
s = append(s, p.Value.(internalRingItem).Value)
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
return s
|
||||
return r.TRing.SlicePrev()
|
||||
}
|
||||
|
||||
244
container/gring/gring_t.go
Normal file
244
container/gring/gring_t.go
Normal file
@ -0,0 +1,244 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gring
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
)
|
||||
|
||||
// TRing is a struct of ring structure.
|
||||
type TRing[T any] struct {
|
||||
mu *rwmutex.RWMutex
|
||||
ring *ring.Ring // Underlying ring.
|
||||
len *gtype.Int // Length(already used size).
|
||||
cap *gtype.Int // Capability(>=len).
|
||||
dirty *gtype.Bool // Dirty, which means the len and cap should be recalculated. It's marked dirty when the size of ring changes.
|
||||
}
|
||||
|
||||
// internalTRingItem[T] stores the ring element value.
|
||||
type internalTRingItem[T any] struct {
|
||||
Value T
|
||||
}
|
||||
|
||||
// NewTRing creates and returns a Ring structure of `cap` elements.
|
||||
// The optional parameter `safe` specifies whether using this structure in concurrent safety,
|
||||
// which is false in default.
|
||||
func NewTRing[T any](cap int, safe ...bool) *TRing[T] {
|
||||
return &TRing[T]{
|
||||
mu: rwmutex.New(safe...),
|
||||
ring: ring.New(cap),
|
||||
len: gtype.NewInt(),
|
||||
cap: gtype.NewInt(cap),
|
||||
dirty: gtype.NewBool(),
|
||||
}
|
||||
}
|
||||
|
||||
// Val returns the item's value of current position.
|
||||
func (r *TRing[T]) Val() T {
|
||||
var value T
|
||||
r.mu.RLock()
|
||||
if r.ring.Value != nil {
|
||||
value = r.ring.Value.(internalTRingItem[T]).Value
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
return value
|
||||
}
|
||||
|
||||
// Len returns the size of ring.
|
||||
func (r *TRing[T]) Len() int {
|
||||
r.checkAndUpdateLenAndCap()
|
||||
return r.len.Val()
|
||||
}
|
||||
|
||||
// Cap returns the capacity of ring.
|
||||
func (r *TRing[T]) Cap() int {
|
||||
r.checkAndUpdateLenAndCap()
|
||||
return r.cap.Val()
|
||||
}
|
||||
|
||||
// Checks and updates the len and cap of ring when ring is dirty.
|
||||
func (r *TRing[T]) checkAndUpdateLenAndCap() {
|
||||
if !r.dirty.Val() {
|
||||
return
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
totalLen := 0
|
||||
emptyLen := 0
|
||||
if r.ring != nil {
|
||||
if r.ring.Value == nil {
|
||||
emptyLen++
|
||||
}
|
||||
totalLen++
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if p.Value == nil {
|
||||
emptyLen++
|
||||
}
|
||||
totalLen++
|
||||
}
|
||||
}
|
||||
r.cap.Set(totalLen)
|
||||
r.len.Set(totalLen - emptyLen)
|
||||
r.dirty.Set(false)
|
||||
}
|
||||
|
||||
// Set sets value to the item of current position.
|
||||
func (r *TRing[T]) Set(value T) *TRing[T] {
|
||||
r.mu.Lock()
|
||||
if r.ring.Value == nil {
|
||||
r.len.Add(1)
|
||||
}
|
||||
r.ring.Value = internalTRingItem[T]{Value: value}
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// Put sets `value` to current item of ring and moves position to next item.
|
||||
func (r *TRing[T]) Put(value T) *TRing[T] {
|
||||
r.mu.Lock()
|
||||
if r.ring.Value == nil {
|
||||
r.len.Add(1)
|
||||
}
|
||||
r.ring.Value = internalTRingItem[T]{Value: value}
|
||||
r.ring = r.ring.Next()
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// Move moves n % r.Len() elements backward (n < 0) or forward (n >= 0)
|
||||
// in the ring and returns that ring element. r must not be empty.
|
||||
func (r *TRing[T]) Move(n int) *TRing[T] {
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Move(n)
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// Prev returns the previous ring element. r must not be empty.
|
||||
func (r *TRing[T]) Prev() *TRing[T] {
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Prev()
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// Next returns the next ring element. r must not be empty.
|
||||
func (r *TRing[T]) Next() *TRing[T] {
|
||||
r.mu.Lock()
|
||||
r.ring = r.ring.Next()
|
||||
r.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// Link connects ring r with ring s such that r.Next()
|
||||
// becomes s and returns the original value for r.Next().
|
||||
// r must not be empty.
|
||||
//
|
||||
// If r and s point to the same ring, linking
|
||||
// them removes the elements between r and s from the ring.
|
||||
// The removed elements form a sub-ring and the result is a
|
||||
// reference to that sub-ring (if no elements were removed,
|
||||
// the result is still the original value for r.Next(),
|
||||
// and not nil).
|
||||
//
|
||||
// If r and s point to different rings, linking
|
||||
// them creates a single ring with the elements of s inserted
|
||||
// after r. The result points to the element following the
|
||||
// last element of s after insertion.
|
||||
func (r *TRing[T]) Link(s *TRing[T]) *TRing[T] {
|
||||
r.mu.Lock()
|
||||
s.mu.Lock()
|
||||
r.ring.Link(s.ring)
|
||||
s.mu.Unlock()
|
||||
r.mu.Unlock()
|
||||
r.dirty.Set(true)
|
||||
s.dirty.Set(true)
|
||||
return r
|
||||
}
|
||||
|
||||
// Unlink removes n % r.Len() elements from the ring r, starting
|
||||
// at r.Next(). If n % r.Len() == 0, r remains unchanged.
|
||||
// The result is the removed sub-ring. r must not be empty.
|
||||
func (r *TRing[T]) Unlink(n int) *TRing[T] {
|
||||
r.mu.Lock()
|
||||
resultRing := r.ring.Unlink(n)
|
||||
r.dirty.Set(true)
|
||||
r.mu.Unlock()
|
||||
resultGRing := NewTRing[T](resultRing.Len())
|
||||
resultGRing.ring = resultRing
|
||||
resultGRing.dirty.Set(true)
|
||||
return resultGRing
|
||||
}
|
||||
|
||||
// RLockIteratorNext iterates and locks reading forward
|
||||
// with given callback function `f` within RWMutex.RLock.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (r *TRing[T]) RLockIteratorNext(f func(value T) bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if r.ring.Value != nil && !f(r.ring.Value.(internalTRingItem[T]).Value) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if p.Value == nil || !f(p.Value.(internalTRingItem[T]).Value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RLockIteratorPrev iterates and locks reading backward
|
||||
// with given callback function `f` within RWMutex.RLock.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (r *TRing[T]) RLockIteratorPrev(f func(value T) bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if r.ring.Value != nil && !f(r.ring.Value.(internalTRingItem[T]).Value) {
|
||||
return
|
||||
}
|
||||
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
|
||||
if p.Value == nil || !f(p.Value.(internalTRingItem[T]).Value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SliceNext returns a copy of all item values as slice forward from current position.
|
||||
func (r *TRing[T]) SliceNext() []T {
|
||||
s := make([]T, 0, r.Len())
|
||||
r.mu.RLock()
|
||||
if r.ring.Value != nil {
|
||||
s = append(s, r.ring.Value.(internalTRingItem[T]).Value)
|
||||
}
|
||||
for p := r.ring.Next(); p != r.ring; p = p.Next() {
|
||||
if p.Value == nil {
|
||||
break
|
||||
}
|
||||
s = append(s, p.Value.(internalTRingItem[T]).Value)
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
return s
|
||||
}
|
||||
|
||||
// SlicePrev returns a copy of all item values as slice backward from current position.
|
||||
func (r *TRing[T]) SlicePrev() []T {
|
||||
s := make([]T, 0, r.Len())
|
||||
r.mu.RLock()
|
||||
if r.ring.Value != nil {
|
||||
s = append(s, r.ring.Value.(internalTRingItem[T]).Value)
|
||||
}
|
||||
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
|
||||
if p.Value == nil {
|
||||
break
|
||||
}
|
||||
s = append(s, p.Value.(internalTRingItem[T]).Value)
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
return s
|
||||
}
|
||||
@ -148,14 +148,11 @@ func Test_Issue1394(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
gRing.Put(i)
|
||||
}
|
||||
t.Logf("the length:%d", gRing.Len())
|
||||
gRingResult := gRing.Unlink(6)
|
||||
for i := 0; i < 10; i++ {
|
||||
t.Log(gRing.Val())
|
||||
gRing = gRing.Next()
|
||||
}
|
||||
t.Logf("the ring length:%d", gRing.Len())
|
||||
t.Logf("the result length:%d", gRingResult.Len())
|
||||
|
||||
// stdring
|
||||
stdRing := ring.New(10)
|
||||
@ -163,14 +160,11 @@ func Test_Issue1394(t *testing.T) {
|
||||
stdRing.Value = i
|
||||
stdRing = stdRing.Next()
|
||||
}
|
||||
t.Logf("the length:%d", stdRing.Len())
|
||||
stdRingResult := stdRing.Unlink(6)
|
||||
for i := 0; i < 10; i++ {
|
||||
t.Log(stdRing.Value)
|
||||
stdRing = stdRing.Next()
|
||||
}
|
||||
t.Logf("the ring length:%d", stdRing.Len())
|
||||
t.Logf("the result length:%d", stdRingResult.Len())
|
||||
|
||||
// Assertion.
|
||||
t.Assert(gRing.Len(), stdRing.Len())
|
||||
|
||||
@ -8,18 +8,15 @@
|
||||
package gset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Set is consisted of any items.
|
||||
type Set struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[any]struct{}
|
||||
*TSet[any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// New create and returns a new set, which contains un-repeated items.
|
||||
@ -33,44 +30,38 @@ func New(safe ...bool) *Set {
|
||||
// Also see New.
|
||||
func NewSet(safe ...bool) *Set {
|
||||
return &Set{
|
||||
data: make(map[any]struct{}),
|
||||
mu: rwmutex.Create(safe...),
|
||||
TSet: NewTSet[any](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// NewFrom returns a new set from `items`.
|
||||
// Parameter `items` can be either a variable of any type, or a slice.
|
||||
func NewFrom(items any, safe ...bool) *Set {
|
||||
m := make(map[any]struct{})
|
||||
for _, v := range gconv.Interfaces(items) {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return &Set{
|
||||
data: m,
|
||||
mu: rwmutex.Create(safe...),
|
||||
TSet: NewTSetFrom[any](gconv.Interfaces(items), safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the set.
|
||||
func (a *Set) lazyInit() {
|
||||
a.once.Do(func() {
|
||||
if a.TSet == nil {
|
||||
a.TSet = NewTSet[any]()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the set readonly with given callback function `f`,
|
||||
// if `f` returns true then continue iterating; or false to stop.
|
||||
func (set *Set) Iterator(f func(v any) bool) {
|
||||
for _, k := range set.Slice() {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
}
|
||||
set.lazyInit()
|
||||
set.TSet.Iterator(f)
|
||||
}
|
||||
|
||||
// Add adds one or multiple items to the set.
|
||||
func (set *Set) Add(items ...any) {
|
||||
set.mu.Lock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[any]struct{})
|
||||
}
|
||||
for _, v := range items {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
set.mu.Unlock()
|
||||
set.lazyInit()
|
||||
set.TSet.Add(items...)
|
||||
}
|
||||
|
||||
// AddIfNotExist checks whether item exists in the set,
|
||||
@ -79,21 +70,8 @@ func (set *Set) Add(items ...any) {
|
||||
//
|
||||
// Note that, if `item` is nil, it does nothing and returns false.
|
||||
func (set *Set) AddIfNotExist(item any) bool {
|
||||
if item == nil {
|
||||
return false
|
||||
}
|
||||
if !set.Contains(item) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[any]struct{})
|
||||
}
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
set.lazyInit()
|
||||
return set.TSet.AddIfNotExist(item)
|
||||
}
|
||||
|
||||
// AddIfNotExistFunc checks whether item exists in the set,
|
||||
@ -103,23 +81,8 @@ func (set *Set) AddIfNotExist(item any) bool {
|
||||
// Note that, if `item` is nil, it does nothing and returns false. The function `f`
|
||||
// is executed without writing lock.
|
||||
func (set *Set) AddIfNotExistFunc(item any, f func() bool) bool {
|
||||
if item == nil {
|
||||
return false
|
||||
}
|
||||
if !set.Contains(item) {
|
||||
if f() {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[any]struct{})
|
||||
}
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
set.lazyInit()
|
||||
return set.TSet.AddIfNotExistFunc(item, f)
|
||||
}
|
||||
|
||||
// AddIfNotExistFuncLock checks whether item exists in the set,
|
||||
@ -129,95 +92,44 @@ func (set *Set) AddIfNotExistFunc(item any, f func() bool) bool {
|
||||
// Note that, if `item` is nil, it does nothing and returns false. The function `f`
|
||||
// is executed within writing lock.
|
||||
func (set *Set) AddIfNotExistFuncLock(item any, f func() bool) bool {
|
||||
if item == nil {
|
||||
return false
|
||||
}
|
||||
if !set.Contains(item) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[any]struct{})
|
||||
}
|
||||
if f() {
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
set.lazyInit()
|
||||
return set.TSet.AddIfNotExistFuncLock(item, f)
|
||||
}
|
||||
|
||||
// Contains checks whether the set contains `item`.
|
||||
func (set *Set) Contains(item any) bool {
|
||||
var ok bool
|
||||
set.mu.RLock()
|
||||
if set.data != nil {
|
||||
_, ok = set.data[item]
|
||||
}
|
||||
set.mu.RUnlock()
|
||||
return ok
|
||||
set.lazyInit()
|
||||
return set.TSet.Contains(item)
|
||||
}
|
||||
|
||||
// Remove deletes `item` from set.
|
||||
func (set *Set) Remove(item any) {
|
||||
set.mu.Lock()
|
||||
if set.data != nil {
|
||||
delete(set.data, item)
|
||||
}
|
||||
set.mu.Unlock()
|
||||
set.lazyInit()
|
||||
set.TSet.Remove(item)
|
||||
}
|
||||
|
||||
// Size returns the size of the set.
|
||||
func (set *Set) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.data)
|
||||
set.mu.RUnlock()
|
||||
return l
|
||||
set.lazyInit()
|
||||
return set.TSet.Size()
|
||||
}
|
||||
|
||||
// Clear deletes all items of the set.
|
||||
func (set *Set) Clear() {
|
||||
set.mu.Lock()
|
||||
set.data = make(map[any]struct{})
|
||||
set.mu.Unlock()
|
||||
set.lazyInit()
|
||||
set.TSet.Clear()
|
||||
}
|
||||
|
||||
// Slice returns all items of the set as slice.
|
||||
func (set *Set) Slice() []any {
|
||||
set.mu.RLock()
|
||||
var (
|
||||
i = 0
|
||||
ret = make([]any, len(set.data))
|
||||
)
|
||||
for item := range set.data {
|
||||
ret[i] = item
|
||||
i++
|
||||
}
|
||||
set.mu.RUnlock()
|
||||
return ret
|
||||
set.lazyInit()
|
||||
return set.TSet.Slice()
|
||||
}
|
||||
|
||||
// Join joins items with a string `glue`.
|
||||
func (set *Set) Join(glue string) string {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if len(set.data) == 0 {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
l = len(set.data)
|
||||
i = 0
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
for k := range set.data {
|
||||
buffer.WriteString(gconv.String(k))
|
||||
if i != l-1 {
|
||||
buffer.WriteString(glue)
|
||||
}
|
||||
i++
|
||||
}
|
||||
return buffer.String()
|
||||
set.lazyInit()
|
||||
return set.TSet.Join(glue)
|
||||
}
|
||||
|
||||
// String returns items as a string, which implements like json.Marshal does.
|
||||
@ -225,63 +137,27 @@ func (set *Set) String() string {
|
||||
if set == nil {
|
||||
return ""
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
var (
|
||||
s string
|
||||
l = len(set.data)
|
||||
i = 0
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
buffer.WriteByte('[')
|
||||
for k := range set.data {
|
||||
s = gconv.String(k)
|
||||
if gstr.IsNumeric(s) {
|
||||
buffer.WriteString(s)
|
||||
} else {
|
||||
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
|
||||
}
|
||||
if i != l-1 {
|
||||
buffer.WriteByte(',')
|
||||
}
|
||||
i++
|
||||
}
|
||||
buffer.WriteByte(']')
|
||||
return buffer.String()
|
||||
set.lazyInit()
|
||||
return set.TSet.String()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with callback function `f`.
|
||||
func (set *Set) LockFunc(f func(m map[any]struct{})) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
f(set.data)
|
||||
set.lazyInit()
|
||||
set.TSet.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with callback function `f`.
|
||||
func (set *Set) RLockFunc(f func(m map[any]struct{})) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
f(set.data)
|
||||
set.lazyInit()
|
||||
set.TSet.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Equal checks whether the two sets equal.
|
||||
func (set *Set) Equal(other *Set) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
if len(set.data) != len(other.data) {
|
||||
return false
|
||||
}
|
||||
for key := range set.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
set.lazyInit()
|
||||
other.lazyInit()
|
||||
return set.TSet.Equal(other.TSet)
|
||||
}
|
||||
|
||||
// IsSubsetOf checks whether the current set is a sub-set of `other`.
|
||||
@ -289,85 +165,40 @@ func (set *Set) IsSubsetOf(other *Set) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key := range set.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
set.lazyInit()
|
||||
other.lazyInit()
|
||||
|
||||
return set.TSet.IsSubsetOf(other.TSet)
|
||||
}
|
||||
|
||||
// Union returns a new set which is the union of `set` and `others`.
|
||||
// Which means, all the items in `newSet` are in `set` or in `others`.
|
||||
func (set *Set) Union(others ...*Set) (newSet *Set) {
|
||||
newSet = NewSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.data {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
if set != other {
|
||||
for k, v := range other.data {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
set.lazyInit()
|
||||
|
||||
return
|
||||
return &Set{
|
||||
TSet: set.TSet.Union(set.toTSetSlice(others)...),
|
||||
}
|
||||
}
|
||||
|
||||
// Diff returns a new set which is the difference set from `set` to `others`.
|
||||
// Which means, all the items in `newSet` are in `set` but not in `others`.
|
||||
func (set *Set) Diff(others ...*Set) (newSet *Set) {
|
||||
newSet = NewSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set == other {
|
||||
continue
|
||||
}
|
||||
other.mu.RLock()
|
||||
for k, v := range set.data {
|
||||
if _, ok := other.data[k]; !ok {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
other.mu.RUnlock()
|
||||
set.lazyInit()
|
||||
|
||||
return &Set{
|
||||
TSet: set.TSet.Diff(set.toTSetSlice(others)...),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Intersect returns a new set which is the intersection from `set` to `others`.
|
||||
// Which means, all the items in `newSet` are in `set` and also in `others`.
|
||||
func (set *Set) Intersect(others ...*Set) (newSet *Set) {
|
||||
newSet = NewSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.data {
|
||||
if _, ok := other.data[k]; ok {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
set.lazyInit()
|
||||
return &Set{
|
||||
TSet: set.TSet.Intersect(set.toTSetSlice(others)...),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Complement returns a new set which is the complement from `set` to `full`.
|
||||
@ -376,36 +207,22 @@ func (set *Set) Intersect(others ...*Set) (newSet *Set) {
|
||||
// It returns the difference between `full` and `set`
|
||||
// if the given set `full` is not the full set of `set`.
|
||||
func (set *Set) Complement(full *Set) (newSet *Set) {
|
||||
newSet = NewSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if set != full {
|
||||
full.mu.RLock()
|
||||
defer full.mu.RUnlock()
|
||||
}
|
||||
for k, v := range full.data {
|
||||
if _, ok := set.data[k]; !ok {
|
||||
newSet.data[k] = v
|
||||
set.lazyInit()
|
||||
if full == nil {
|
||||
return &Set{
|
||||
TSet: NewTSet[any](true),
|
||||
}
|
||||
}
|
||||
return
|
||||
full.lazyInit()
|
||||
return &Set{
|
||||
TSet: set.TSet.Complement(full.TSet),
|
||||
}
|
||||
}
|
||||
|
||||
// Merge adds items from `others` sets into `set`.
|
||||
func (set *Set) Merge(others ...*Set) *Set {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
set.data[k] = v
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
set.lazyInit()
|
||||
set.TSet.Merge(set.toTSetSlice(others)...)
|
||||
return set
|
||||
}
|
||||
|
||||
@ -413,101 +230,46 @@ func (set *Set) Merge(others ...*Set) *Set {
|
||||
// Note: The items should be converted to int type,
|
||||
// or you'd get a result that you unexpected.
|
||||
func (set *Set) Sum() (sum int) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k := range set.data {
|
||||
sum += gconv.Int(k)
|
||||
}
|
||||
return
|
||||
set.lazyInit()
|
||||
return set.TSet.Sum()
|
||||
}
|
||||
|
||||
// Pop randomly pops an item from set.
|
||||
func (set *Set) Pop() any {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for k := range set.data {
|
||||
delete(set.data, k)
|
||||
return k
|
||||
}
|
||||
return nil
|
||||
set.lazyInit()
|
||||
return set.TSet.Pop()
|
||||
}
|
||||
|
||||
// Pops randomly pops `size` items from set.
|
||||
// It returns all items if size == -1.
|
||||
func (set *Set) Pops(size int) []any {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if size > len(set.data) || size == -1 {
|
||||
size = len(set.data)
|
||||
}
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
index := 0
|
||||
array := make([]any, size)
|
||||
for k := range set.data {
|
||||
delete(set.data, k)
|
||||
array[index] = k
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
set.lazyInit()
|
||||
return set.TSet.Pops(size)
|
||||
}
|
||||
|
||||
// Walk applies a user supplied function `f` to every item of set.
|
||||
func (set *Set) Walk(f func(item any) any) *Set {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
m := make(map[any]struct{}, len(set.data))
|
||||
for k, v := range set.data {
|
||||
m[f(k)] = v
|
||||
}
|
||||
set.data = m
|
||||
set.lazyInit()
|
||||
set.TSet.Walk(f)
|
||||
return set
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (set Set) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(set.Slice())
|
||||
set.lazyInit()
|
||||
return set.TSet.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (set *Set) UnmarshalJSON(b []byte) error {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[any]struct{})
|
||||
}
|
||||
var array []any
|
||||
if err := json.UnmarshalUseNumber(b, &array); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range array {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
set.lazyInit()
|
||||
return set.TSet.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for set.
|
||||
func (set *Set) UnmarshalValue(value any) (err error) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[any]struct{})
|
||||
}
|
||||
var array []any
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
err = json.UnmarshalUseNumber(gconv.Bytes(value), &array)
|
||||
default:
|
||||
array = gconv.SliceAny(value)
|
||||
}
|
||||
for _, v := range array {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
return
|
||||
set.lazyInit()
|
||||
return set.TSet.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
@ -515,11 +277,21 @@ func (set *Set) DeepCopy() any {
|
||||
if set == nil {
|
||||
return nil
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
data := make([]any, 0)
|
||||
for k := range set.data {
|
||||
data = append(data, k)
|
||||
set.lazyInit()
|
||||
return &Set{
|
||||
TSet: set.TSet.DeepCopy().(*TSet[any]),
|
||||
}
|
||||
return NewFrom(data, set.mu.IsSafe())
|
||||
}
|
||||
|
||||
// toTSetSlice converts []*Set to []*TSet[any]
|
||||
func (set *Set) toTSetSlice(sets []*Set) (tSets []*TSet[any]) {
|
||||
tSets = make([]*TSet[any], len(sets))
|
||||
for i, v := range sets {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
v.lazyInit()
|
||||
tSets[i] = v.TSet
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -8,17 +8,13 @@
|
||||
package gset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// IntSet is consisted of int items.
|
||||
type IntSet struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[int]struct{}
|
||||
*TSet[int]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewIntSet create and returns a new set, which contains un-repeated items.
|
||||
@ -26,43 +22,37 @@ type IntSet struct {
|
||||
// which is false in default.
|
||||
func NewIntSet(safe ...bool) *IntSet {
|
||||
return &IntSet{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[int]struct{}),
|
||||
TSet: NewTSet[int](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// NewIntSetFrom returns a new set from `items`.
|
||||
func NewIntSetFrom(items []int, safe ...bool) *IntSet {
|
||||
m := make(map[int]struct{})
|
||||
for _, v := range items {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return &IntSet{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: m,
|
||||
TSet: NewTSetFrom(items, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the set.
|
||||
func (a *IntSet) lazyInit() {
|
||||
a.once.Do(func() {
|
||||
if a.TSet == nil {
|
||||
a.TSet = NewTSet[int]()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the set readonly with given callback function `f`,
|
||||
// if `f` returns true then continue iterating; or false to stop.
|
||||
func (set *IntSet) Iterator(f func(v int) bool) {
|
||||
for _, k := range set.Slice() {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
}
|
||||
set.lazyInit()
|
||||
set.TSet.Iterator(f)
|
||||
}
|
||||
|
||||
// Add adds one or multiple items to the set.
|
||||
func (set *IntSet) Add(item ...int) {
|
||||
set.mu.Lock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[int]struct{})
|
||||
}
|
||||
for _, v := range item {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
set.mu.Unlock()
|
||||
set.lazyInit()
|
||||
set.TSet.Add(item...)
|
||||
}
|
||||
|
||||
// AddIfNotExist checks whether item exists in the set,
|
||||
@ -71,18 +61,8 @@ func (set *IntSet) Add(item ...int) {
|
||||
//
|
||||
// Note that, if `item` is nil, it does nothing and returns false.
|
||||
func (set *IntSet) AddIfNotExist(item int) bool {
|
||||
if !set.Contains(item) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[int]struct{})
|
||||
}
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
set.lazyInit()
|
||||
return set.TSet.AddIfNotExist(item)
|
||||
}
|
||||
|
||||
// AddIfNotExistFunc checks whether item exists in the set,
|
||||
@ -91,20 +71,8 @@ func (set *IntSet) AddIfNotExist(item int) bool {
|
||||
//
|
||||
// Note that, the function `f` is executed without writing lock.
|
||||
func (set *IntSet) AddIfNotExistFunc(item int, f func() bool) bool {
|
||||
if !set.Contains(item) {
|
||||
if f() {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[int]struct{})
|
||||
}
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
set.lazyInit()
|
||||
return set.TSet.AddIfNotExistFunc(item, f)
|
||||
}
|
||||
|
||||
// AddIfNotExistFuncLock checks whether item exists in the set,
|
||||
@ -113,92 +81,44 @@ func (set *IntSet) AddIfNotExistFunc(item int, f func() bool) bool {
|
||||
//
|
||||
// Note that, the function `f` is executed without writing lock.
|
||||
func (set *IntSet) AddIfNotExistFuncLock(item int, f func() bool) bool {
|
||||
if !set.Contains(item) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[int]struct{})
|
||||
}
|
||||
if f() {
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
set.lazyInit()
|
||||
return set.TSet.AddIfNotExistFuncLock(item, f)
|
||||
}
|
||||
|
||||
// Contains checks whether the set contains `item`.
|
||||
func (set *IntSet) Contains(item int) bool {
|
||||
var ok bool
|
||||
set.mu.RLock()
|
||||
if set.data != nil {
|
||||
_, ok = set.data[item]
|
||||
}
|
||||
set.mu.RUnlock()
|
||||
return ok
|
||||
set.lazyInit()
|
||||
return set.TSet.Contains(item)
|
||||
}
|
||||
|
||||
// Remove deletes `item` from set.
|
||||
func (set *IntSet) Remove(item int) {
|
||||
set.mu.Lock()
|
||||
if set.data != nil {
|
||||
delete(set.data, item)
|
||||
}
|
||||
set.mu.Unlock()
|
||||
set.lazyInit()
|
||||
set.TSet.Remove(item)
|
||||
}
|
||||
|
||||
// Size returns the size of the set.
|
||||
func (set *IntSet) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.data)
|
||||
set.mu.RUnlock()
|
||||
return l
|
||||
set.lazyInit()
|
||||
return set.TSet.Size()
|
||||
}
|
||||
|
||||
// Clear deletes all items of the set.
|
||||
func (set *IntSet) Clear() {
|
||||
set.mu.Lock()
|
||||
set.data = make(map[int]struct{})
|
||||
set.mu.Unlock()
|
||||
set.lazyInit()
|
||||
set.TSet.Clear()
|
||||
}
|
||||
|
||||
// Slice returns the an of items of the set as slice.
|
||||
func (set *IntSet) Slice() []int {
|
||||
set.mu.RLock()
|
||||
var (
|
||||
i = 0
|
||||
ret = make([]int, len(set.data))
|
||||
)
|
||||
for k := range set.data {
|
||||
ret[i] = k
|
||||
i++
|
||||
}
|
||||
set.mu.RUnlock()
|
||||
return ret
|
||||
set.lazyInit()
|
||||
return set.TSet.Slice()
|
||||
}
|
||||
|
||||
// Join joins items with a string `glue`.
|
||||
func (set *IntSet) Join(glue string) string {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if len(set.data) == 0 {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
l = len(set.data)
|
||||
i = 0
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
for k := range set.data {
|
||||
buffer.WriteString(gconv.String(k))
|
||||
if i != l-1 {
|
||||
buffer.WriteString(glue)
|
||||
}
|
||||
i++
|
||||
}
|
||||
return buffer.String()
|
||||
set.lazyInit()
|
||||
return set.TSet.Join(glue)
|
||||
}
|
||||
|
||||
// String returns items as a string, which implements like json.Marshal does.
|
||||
@ -206,41 +126,27 @@ func (set *IntSet) String() string {
|
||||
if set == nil {
|
||||
return ""
|
||||
}
|
||||
return "[" + set.Join(",") + "]"
|
||||
set.lazyInit()
|
||||
return set.TSet.String()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with callback function `f`.
|
||||
func (set *IntSet) LockFunc(f func(m map[int]struct{})) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
f(set.data)
|
||||
set.lazyInit()
|
||||
set.TSet.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with callback function `f`.
|
||||
func (set *IntSet) RLockFunc(f func(m map[int]struct{})) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
f(set.data)
|
||||
set.lazyInit()
|
||||
set.TSet.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Equal checks whether the two sets equal.
|
||||
func (set *IntSet) Equal(other *IntSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
if len(set.data) != len(other.data) {
|
||||
return false
|
||||
}
|
||||
for key := range set.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
set.lazyInit()
|
||||
other.lazyInit()
|
||||
return set.TSet.Equal(other.TSet)
|
||||
}
|
||||
|
||||
// IsSubsetOf checks whether the current set is a sub-set of `other`.
|
||||
@ -248,85 +154,38 @@ func (set *IntSet) IsSubsetOf(other *IntSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key := range set.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
set.lazyInit()
|
||||
other.lazyInit()
|
||||
|
||||
return set.TSet.IsSubsetOf(other.TSet)
|
||||
}
|
||||
|
||||
// Union returns a new set which is the union of `set` and `other`.
|
||||
// Which means, all the items in `newSet` are in `set` or in `other`.
|
||||
func (set *IntSet) Union(others ...*IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.data {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
if set != other {
|
||||
for k, v := range other.data {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
set.lazyInit()
|
||||
return &IntSet{
|
||||
TSet: set.TSet.Union(set.toTSetSlice(others)...),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Diff returns a new set which is the difference set from `set` to `other`.
|
||||
// Which means, all the items in `newSet` are in `set` but not in `other`.
|
||||
func (set *IntSet) Diff(others ...*IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set == other {
|
||||
continue
|
||||
}
|
||||
other.mu.RLock()
|
||||
for k, v := range set.data {
|
||||
if _, ok := other.data[k]; !ok {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
other.mu.RUnlock()
|
||||
set.lazyInit()
|
||||
return &IntSet{
|
||||
TSet: set.TSet.Diff(set.toTSetSlice(others)...),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Intersect returns a new set which is the intersection from `set` to `other`.
|
||||
// Which means, all the items in `newSet` are in `set` and also in `other`.
|
||||
func (set *IntSet) Intersect(others ...*IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.data {
|
||||
if _, ok := other.data[k]; ok {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
set.lazyInit()
|
||||
return &IntSet{
|
||||
TSet: set.TSet.Intersect(set.toTSetSlice(others)...),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Complement returns a new set which is the complement from `set` to `full`.
|
||||
@ -335,36 +194,22 @@ func (set *IntSet) Intersect(others ...*IntSet) (newSet *IntSet) {
|
||||
// It returns the difference between `full` and `set`
|
||||
// if the given set `full` is not the full set of `set`.
|
||||
func (set *IntSet) Complement(full *IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if set != full {
|
||||
full.mu.RLock()
|
||||
defer full.mu.RUnlock()
|
||||
}
|
||||
for k, v := range full.data {
|
||||
if _, ok := set.data[k]; !ok {
|
||||
newSet.data[k] = v
|
||||
set.lazyInit()
|
||||
if full == nil {
|
||||
return &IntSet{
|
||||
TSet: NewTSet[int](),
|
||||
}
|
||||
}
|
||||
return
|
||||
full.lazyInit()
|
||||
return &IntSet{
|
||||
TSet: set.TSet.Complement(full.TSet),
|
||||
}
|
||||
}
|
||||
|
||||
// Merge adds items from `others` sets into `set`.
|
||||
func (set *IntSet) Merge(others ...*IntSet) *IntSet {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
set.data[k] = v
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
set.lazyInit()
|
||||
set.TSet.Merge(set.toTSetSlice(others)...)
|
||||
return set
|
||||
}
|
||||
|
||||
@ -372,101 +217,46 @@ func (set *IntSet) Merge(others ...*IntSet) *IntSet {
|
||||
// Note: The items should be converted to int type,
|
||||
// or you'd get a result that you unexpected.
|
||||
func (set *IntSet) Sum() (sum int) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k := range set.data {
|
||||
sum += k
|
||||
}
|
||||
return
|
||||
set.lazyInit()
|
||||
return set.TSet.Sum()
|
||||
}
|
||||
|
||||
// Pop randomly pops an item from set.
|
||||
func (set *IntSet) Pop() int {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for k := range set.data {
|
||||
delete(set.data, k)
|
||||
return k
|
||||
}
|
||||
return 0
|
||||
set.lazyInit()
|
||||
return set.TSet.Pop()
|
||||
}
|
||||
|
||||
// Pops randomly pops `size` items from set.
|
||||
// It returns all items if size == -1.
|
||||
func (set *IntSet) Pops(size int) []int {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if size > len(set.data) || size == -1 {
|
||||
size = len(set.data)
|
||||
}
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
index := 0
|
||||
array := make([]int, size)
|
||||
for k := range set.data {
|
||||
delete(set.data, k)
|
||||
array[index] = k
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
set.lazyInit()
|
||||
return set.TSet.Pops(size)
|
||||
}
|
||||
|
||||
// Walk applies a user supplied function `f` to every item of set.
|
||||
func (set *IntSet) Walk(f func(item int) int) *IntSet {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
m := make(map[int]struct{}, len(set.data))
|
||||
for k, v := range set.data {
|
||||
m[f(k)] = v
|
||||
}
|
||||
set.data = m
|
||||
set.lazyInit()
|
||||
set.TSet.Walk(f)
|
||||
return set
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (set IntSet) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(set.Slice())
|
||||
set.lazyInit()
|
||||
return set.TSet.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (set *IntSet) UnmarshalJSON(b []byte) error {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[int]struct{})
|
||||
}
|
||||
var array []int
|
||||
if err := json.UnmarshalUseNumber(b, &array); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range array {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
set.lazyInit()
|
||||
return set.TSet.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for set.
|
||||
func (set *IntSet) UnmarshalValue(value any) (err error) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[int]struct{})
|
||||
}
|
||||
var array []int
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
err = json.UnmarshalUseNumber(gconv.Bytes(value), &array)
|
||||
default:
|
||||
array = gconv.SliceInt(value)
|
||||
}
|
||||
for _, v := range array {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
return
|
||||
set.lazyInit()
|
||||
return set.TSet.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
@ -474,15 +264,21 @@ func (set *IntSet) DeepCopy() any {
|
||||
if set == nil {
|
||||
return nil
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
var (
|
||||
slice = make([]int, len(set.data))
|
||||
index = 0
|
||||
)
|
||||
for k := range set.data {
|
||||
slice[index] = k
|
||||
index++
|
||||
set.lazyInit()
|
||||
return &IntSet{
|
||||
TSet: set.TSet.DeepCopy().(*TSet[int]),
|
||||
}
|
||||
return NewIntSetFrom(slice, set.mu.IsSafe())
|
||||
}
|
||||
|
||||
// toTSetSlice converts []*IntSet to []*TSet[int]
|
||||
func (set *IntSet) toTSetSlice(sets []*IntSet) (tSets []*TSet[int]) {
|
||||
tSets = make([]*TSet[int], len(sets))
|
||||
for i, v := range sets {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
v.lazyInit()
|
||||
tSets[i] = v.TSet
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -8,19 +8,14 @@
|
||||
package gset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// StrSet is consisted of string items.
|
||||
type StrSet struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[string]struct{}
|
||||
*TSet[string]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewStrSet create and returns a new set, which contains un-repeated items.
|
||||
@ -28,61 +23,45 @@ type StrSet struct {
|
||||
// which is false in default.
|
||||
func NewStrSet(safe ...bool) *StrSet {
|
||||
return &StrSet{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[string]struct{}),
|
||||
TSet: NewTSet[string](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// NewStrSetFrom returns a new set from `items`.
|
||||
func NewStrSetFrom(items []string, safe ...bool) *StrSet {
|
||||
m := make(map[string]struct{})
|
||||
for _, v := range items {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return &StrSet{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: m,
|
||||
TSet: NewTSetFrom(items, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the set.
|
||||
func (a *StrSet) lazyInit() {
|
||||
a.once.Do(func() {
|
||||
if a.TSet == nil {
|
||||
a.TSet = NewTSet[string]()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the set readonly with given callback function `f`,
|
||||
// if `f` returns true then continue iterating; or false to stop.
|
||||
func (set *StrSet) Iterator(f func(v string) bool) {
|
||||
for _, k := range set.Slice() {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
}
|
||||
set.lazyInit()
|
||||
set.TSet.Iterator(f)
|
||||
}
|
||||
|
||||
// Add adds one or multiple items to the set.
|
||||
func (set *StrSet) Add(item ...string) {
|
||||
set.mu.Lock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[string]struct{})
|
||||
}
|
||||
for _, v := range item {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
set.mu.Unlock()
|
||||
set.lazyInit()
|
||||
set.TSet.Add(item...)
|
||||
}
|
||||
|
||||
// AddIfNotExist checks whether item exists in the set,
|
||||
// it adds the item to set and returns true if it does not exist in the set,
|
||||
// or else it does nothing and returns false.
|
||||
func (set *StrSet) AddIfNotExist(item string) bool {
|
||||
if !set.Contains(item) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[string]struct{})
|
||||
}
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
set.lazyInit()
|
||||
return set.TSet.AddIfNotExist(item)
|
||||
}
|
||||
|
||||
// AddIfNotExistFunc checks whether item exists in the set,
|
||||
@ -91,20 +70,8 @@ func (set *StrSet) AddIfNotExist(item string) bool {
|
||||
//
|
||||
// Note that, the function `f` is executed without writing lock.
|
||||
func (set *StrSet) AddIfNotExistFunc(item string, f func() bool) bool {
|
||||
if !set.Contains(item) {
|
||||
if f() {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[string]struct{})
|
||||
}
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
set.lazyInit()
|
||||
return set.TSet.AddIfNotExistFunc(item, f)
|
||||
}
|
||||
|
||||
// AddIfNotExistFuncLock checks whether item exists in the set,
|
||||
@ -113,36 +80,20 @@ func (set *StrSet) AddIfNotExistFunc(item string, f func() bool) bool {
|
||||
//
|
||||
// Note that, the function `f` is executed without writing lock.
|
||||
func (set *StrSet) AddIfNotExistFuncLock(item string, f func() bool) bool {
|
||||
if !set.Contains(item) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[string]struct{})
|
||||
}
|
||||
if f() {
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
set.lazyInit()
|
||||
return set.TSet.AddIfNotExistFuncLock(item, f)
|
||||
}
|
||||
|
||||
// Contains checks whether the set contains `item`.
|
||||
func (set *StrSet) Contains(item string) bool {
|
||||
var ok bool
|
||||
set.mu.RLock()
|
||||
if set.data != nil {
|
||||
_, ok = set.data[item]
|
||||
}
|
||||
set.mu.RUnlock()
|
||||
return ok
|
||||
set.lazyInit()
|
||||
return set.TSet.Contains(item)
|
||||
}
|
||||
|
||||
// ContainsI checks whether a value exists in the set with case-insensitively.
|
||||
// Note that it internally iterates the whole set to do the comparison with case-insensitively.
|
||||
func (set *StrSet) ContainsI(item string) bool {
|
||||
set.lazyInit()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k := range set.data {
|
||||
@ -155,64 +106,32 @@ func (set *StrSet) ContainsI(item string) bool {
|
||||
|
||||
// Remove deletes `item` from set.
|
||||
func (set *StrSet) Remove(item string) {
|
||||
set.mu.Lock()
|
||||
if set.data != nil {
|
||||
delete(set.data, item)
|
||||
}
|
||||
set.mu.Unlock()
|
||||
set.lazyInit()
|
||||
set.TSet.Remove(item)
|
||||
}
|
||||
|
||||
// Size returns the size of the set.
|
||||
func (set *StrSet) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.data)
|
||||
set.mu.RUnlock()
|
||||
return l
|
||||
set.lazyInit()
|
||||
return set.TSet.Size()
|
||||
}
|
||||
|
||||
// Clear deletes all items of the set.
|
||||
func (set *StrSet) Clear() {
|
||||
set.mu.Lock()
|
||||
set.data = make(map[string]struct{})
|
||||
set.mu.Unlock()
|
||||
set.lazyInit()
|
||||
set.TSet.Clear()
|
||||
}
|
||||
|
||||
// Slice returns the an of items of the set as slice.
|
||||
func (set *StrSet) Slice() []string {
|
||||
set.mu.RLock()
|
||||
var (
|
||||
i = 0
|
||||
ret = make([]string, len(set.data))
|
||||
)
|
||||
for item := range set.data {
|
||||
ret[i] = item
|
||||
i++
|
||||
}
|
||||
|
||||
set.mu.RUnlock()
|
||||
return ret
|
||||
set.lazyInit()
|
||||
return set.TSet.Slice()
|
||||
}
|
||||
|
||||
// Join joins items with a string `glue`.
|
||||
func (set *StrSet) Join(glue string) string {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if len(set.data) == 0 {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
l = len(set.data)
|
||||
i = 0
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
for k := range set.data {
|
||||
buffer.WriteString(k)
|
||||
if i != l-1 {
|
||||
buffer.WriteString(glue)
|
||||
}
|
||||
i++
|
||||
}
|
||||
return buffer.String()
|
||||
set.lazyInit()
|
||||
return set.TSet.Join(glue)
|
||||
}
|
||||
|
||||
// String returns items as a string, which implements like json.Marshal does.
|
||||
@ -220,57 +139,27 @@ func (set *StrSet) String() string {
|
||||
if set == nil {
|
||||
return ""
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
var (
|
||||
l = len(set.data)
|
||||
i = 0
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
buffer.WriteByte('[')
|
||||
for k := range set.data {
|
||||
buffer.WriteString(`"` + gstr.QuoteMeta(k, `"\`) + `"`)
|
||||
if i != l-1 {
|
||||
buffer.WriteByte(',')
|
||||
}
|
||||
i++
|
||||
}
|
||||
buffer.WriteByte(']')
|
||||
return buffer.String()
|
||||
set.lazyInit()
|
||||
return set.TSet.String()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with callback function `f`.
|
||||
func (set *StrSet) LockFunc(f func(m map[string]struct{})) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
f(set.data)
|
||||
set.lazyInit()
|
||||
set.TSet.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with callback function `f`.
|
||||
func (set *StrSet) RLockFunc(f func(m map[string]struct{})) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
f(set.data)
|
||||
set.lazyInit()
|
||||
set.TSet.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Equal checks whether the two sets equal.
|
||||
func (set *StrSet) Equal(other *StrSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
if len(set.data) != len(other.data) {
|
||||
return false
|
||||
}
|
||||
for key := range set.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
set.lazyInit()
|
||||
other.lazyInit()
|
||||
return set.TSet.Equal(other.TSet)
|
||||
}
|
||||
|
||||
// IsSubsetOf checks whether the current set is a sub-set of `other`.
|
||||
@ -278,85 +167,38 @@ func (set *StrSet) IsSubsetOf(other *StrSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key := range set.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
set.lazyInit()
|
||||
other.lazyInit()
|
||||
|
||||
return set.TSet.IsSubsetOf(other.TSet)
|
||||
}
|
||||
|
||||
// Union returns a new set which is the union of `set` and `other`.
|
||||
// Which means, all the items in `newSet` are in `set` or in `other`.
|
||||
func (set *StrSet) Union(others ...*StrSet) (newSet *StrSet) {
|
||||
newSet = NewStrSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.data {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
if set != other {
|
||||
for k, v := range other.data {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
set.lazyInit()
|
||||
return &StrSet{
|
||||
TSet: set.TSet.Union(set.toTSetSlice(others)...),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Diff returns a new set which is the difference set from `set` to `other`.
|
||||
// Which means, all the items in `newSet` are in `set` but not in `other`.
|
||||
func (set *StrSet) Diff(others ...*StrSet) (newSet *StrSet) {
|
||||
newSet = NewStrSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set == other {
|
||||
continue
|
||||
}
|
||||
other.mu.RLock()
|
||||
for k, v := range set.data {
|
||||
if _, ok := other.data[k]; !ok {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
other.mu.RUnlock()
|
||||
set.lazyInit()
|
||||
return &StrSet{
|
||||
TSet: set.TSet.Diff(set.toTSetSlice(others)...),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Intersect returns a new set which is the intersection from `set` to `other`.
|
||||
// Which means, all the items in `newSet` are in `set` and also in `other`.
|
||||
func (set *StrSet) Intersect(others ...*StrSet) (newSet *StrSet) {
|
||||
newSet = NewStrSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.data {
|
||||
if _, ok := other.data[k]; ok {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
set.lazyInit()
|
||||
return &StrSet{
|
||||
TSet: set.TSet.Intersect(set.toTSetSlice(others)...),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Complement returns a new set which is the complement from `set` to `full`.
|
||||
@ -365,36 +207,22 @@ func (set *StrSet) Intersect(others ...*StrSet) (newSet *StrSet) {
|
||||
// It returns the difference between `full` and `set`
|
||||
// if the given set `full` is not the full set of `set`.
|
||||
func (set *StrSet) Complement(full *StrSet) (newSet *StrSet) {
|
||||
newSet = NewStrSet()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if set != full {
|
||||
full.mu.RLock()
|
||||
defer full.mu.RUnlock()
|
||||
}
|
||||
for k, v := range full.data {
|
||||
if _, ok := set.data[k]; !ok {
|
||||
newSet.data[k] = v
|
||||
set.lazyInit()
|
||||
if full == nil {
|
||||
return &StrSet{
|
||||
TSet: NewTSet[string](),
|
||||
}
|
||||
}
|
||||
return
|
||||
full.lazyInit()
|
||||
return &StrSet{
|
||||
TSet: set.TSet.Complement(full.TSet),
|
||||
}
|
||||
}
|
||||
|
||||
// Merge adds items from `others` sets into `set`.
|
||||
func (set *StrSet) Merge(others ...*StrSet) *StrSet {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
set.data[k] = v
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
set.lazyInit()
|
||||
set.TSet.Merge(set.toTSetSlice(others)...)
|
||||
return set
|
||||
}
|
||||
|
||||
@ -402,101 +230,46 @@ func (set *StrSet) Merge(others ...*StrSet) *StrSet {
|
||||
// Note: The items should be converted to int type,
|
||||
// or you'd get a result that you unexpected.
|
||||
func (set *StrSet) Sum() (sum int) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k := range set.data {
|
||||
sum += gconv.Int(k)
|
||||
}
|
||||
return
|
||||
set.lazyInit()
|
||||
return set.TSet.Sum()
|
||||
}
|
||||
|
||||
// Pop randomly pops an item from set.
|
||||
func (set *StrSet) Pop() string {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for k := range set.data {
|
||||
delete(set.data, k)
|
||||
return k
|
||||
}
|
||||
return ""
|
||||
set.lazyInit()
|
||||
return set.TSet.Pop()
|
||||
}
|
||||
|
||||
// Pops randomly pops `size` items from set.
|
||||
// It returns all items if size == -1.
|
||||
func (set *StrSet) Pops(size int) []string {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if size > len(set.data) || size == -1 {
|
||||
size = len(set.data)
|
||||
}
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
index := 0
|
||||
array := make([]string, size)
|
||||
for k := range set.data {
|
||||
delete(set.data, k)
|
||||
array[index] = k
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
set.lazyInit()
|
||||
return set.TSet.Pops(size)
|
||||
}
|
||||
|
||||
// Walk applies a user supplied function `f` to every item of set.
|
||||
func (set *StrSet) Walk(f func(item string) string) *StrSet {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
m := make(map[string]struct{}, len(set.data))
|
||||
for k, v := range set.data {
|
||||
m[f(k)] = v
|
||||
}
|
||||
set.data = m
|
||||
set.lazyInit()
|
||||
set.TSet.Walk(f)
|
||||
return set
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (set StrSet) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(set.Slice())
|
||||
set.lazyInit()
|
||||
return set.TSet.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (set *StrSet) UnmarshalJSON(b []byte) error {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[string]struct{})
|
||||
}
|
||||
var array []string
|
||||
if err := json.UnmarshalUseNumber(b, &array); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range array {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
set.lazyInit()
|
||||
return set.TSet.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for set.
|
||||
func (set *StrSet) UnmarshalValue(value any) (err error) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[string]struct{})
|
||||
}
|
||||
var array []string
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
err = json.UnmarshalUseNumber(gconv.Bytes(value), &array)
|
||||
default:
|
||||
array = gconv.SliceStr(value)
|
||||
}
|
||||
for _, v := range array {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
return
|
||||
set.lazyInit()
|
||||
return set.TSet.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
@ -504,15 +277,21 @@ func (set *StrSet) DeepCopy() any {
|
||||
if set == nil {
|
||||
return nil
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
var (
|
||||
slice = make([]string, len(set.data))
|
||||
index = 0
|
||||
)
|
||||
for k := range set.data {
|
||||
slice[index] = k
|
||||
index++
|
||||
set.lazyInit()
|
||||
return &StrSet{
|
||||
TSet: set.TSet.DeepCopy().(*TSet[string]),
|
||||
}
|
||||
return NewStrSetFrom(slice, set.mu.IsSafe())
|
||||
}
|
||||
|
||||
// toTSetSlice converts []*StrSet to []*TSet[string]
|
||||
func (set *StrSet) toTSetSlice(sets []*StrSet) (tSets []*TSet[string]) {
|
||||
tSets = make([]*TSet[string], len(sets))
|
||||
for i, v := range sets {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
v.lazyInit()
|
||||
tSets[i] = v.TSet
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
531
container/gset/gset_t_set.go
Normal file
531
container/gset/gset_t_set.go
Normal file
@ -0,0 +1,531 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// TSet[T] is consisted of any items.
|
||||
type TSet[T comparable] struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[T]struct{}
|
||||
}
|
||||
|
||||
// NewTSet creates and returns a new set, which contains un-repeated items.
|
||||
// Also see New.
|
||||
func NewTSet[T comparable](safe ...bool) *TSet[T] {
|
||||
return &TSet[T]{
|
||||
data: make(map[T]struct{}),
|
||||
mu: rwmutex.Create(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// NewTSetFrom returns a new set from `items`.
|
||||
// `items` - A slice of type T.
|
||||
func NewTSetFrom[T comparable](items []T, safe ...bool) *TSet[T] {
|
||||
m := make(map[T]struct{})
|
||||
for _, v := range items {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return &TSet[T]{
|
||||
data: m,
|
||||
mu: rwmutex.Create(safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the set readonly with given callback function `f`,
|
||||
// if `f` returns true then continue iterating; or false to stop.
|
||||
func (set *TSet[T]) Iterator(f func(v T) bool) {
|
||||
for _, k := range set.Slice() {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds one or multiple items to the set.
|
||||
func (set *TSet[T]) Add(items ...T) {
|
||||
set.mu.Lock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[T]struct{})
|
||||
}
|
||||
for _, v := range items {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
set.mu.Unlock()
|
||||
}
|
||||
|
||||
// AddIfNotExist checks whether item exists in the set,
|
||||
// it adds the item to set and returns true if it does not exists in the set,
|
||||
// or else it does nothing and returns false.
|
||||
//
|
||||
// Note that, if `item` is nil, it does nothing and returns false.
|
||||
func (set *TSet[T]) AddIfNotExist(item T) bool {
|
||||
if any(item) == nil {
|
||||
return false
|
||||
}
|
||||
if !set.Contains(item) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[T]struct{})
|
||||
}
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AddIfNotExistFunc checks whether item exists in the set,
|
||||
// it adds the item to set and returns true if it does not exist in the set and
|
||||
// function `f` returns true, or else it does nothing and returns false.
|
||||
//
|
||||
// Note that, if `item` is nil, it does nothing and returns false. The function `f`
|
||||
// is executed without writing lock.
|
||||
func (set *TSet[T]) AddIfNotExistFunc(item T, f func() bool) bool {
|
||||
if any(item) == nil {
|
||||
return false
|
||||
}
|
||||
if !set.Contains(item) {
|
||||
if f() {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[T]struct{})
|
||||
}
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AddIfNotExistFuncLock checks whether item exists in the set,
|
||||
// it adds the item to set and returns true if it does not exists in the set and
|
||||
// function `f` returns true, or else it does nothing and returns false.
|
||||
//
|
||||
// Note that, if `item` is nil, it does nothing and returns false. The function `f`
|
||||
// is executed within writing lock.
|
||||
func (set *TSet[T]) AddIfNotExistFuncLock(item T, f func() bool) bool {
|
||||
if any(item) == nil {
|
||||
return false
|
||||
}
|
||||
if !set.Contains(item) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[T]struct{})
|
||||
}
|
||||
if f() {
|
||||
if _, ok := set.data[item]; !ok {
|
||||
set.data[item] = struct{}{}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Contains checks whether the set contains `item`.
|
||||
func (set *TSet[T]) Contains(item T) bool {
|
||||
var ok bool
|
||||
set.mu.RLock()
|
||||
if set.data != nil {
|
||||
_, ok = set.data[item]
|
||||
}
|
||||
set.mu.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// Remove deletes `item` from set.
|
||||
func (set *TSet[T]) Remove(item T) {
|
||||
set.mu.Lock()
|
||||
if set.data != nil {
|
||||
delete(set.data, item)
|
||||
}
|
||||
set.mu.Unlock()
|
||||
}
|
||||
|
||||
// Size returns the size of the set.
|
||||
func (set *TSet[T]) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.data)
|
||||
set.mu.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// Clear deletes all items of the set.
|
||||
func (set *TSet[T]) Clear() {
|
||||
set.mu.Lock()
|
||||
set.data = make(map[T]struct{})
|
||||
set.mu.Unlock()
|
||||
}
|
||||
|
||||
// Slice returns all items of the set as slice.
|
||||
func (set *TSet[T]) Slice() []T {
|
||||
set.mu.RLock()
|
||||
var (
|
||||
i = 0
|
||||
ret = make([]T, len(set.data))
|
||||
)
|
||||
for item := range set.data {
|
||||
ret[i] = item
|
||||
i++
|
||||
}
|
||||
set.mu.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
// Join joins items with a string `glue`.
|
||||
func (set *TSet[T]) Join(glue string) string {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if len(set.data) == 0 {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
l = len(set.data)
|
||||
i = 0
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
for k := range set.data {
|
||||
buffer.WriteString(gconv.String(k))
|
||||
if i != l-1 {
|
||||
buffer.WriteString(glue)
|
||||
}
|
||||
i++
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// String returns items as a string, which implements like json.Marshal does.
|
||||
func (set *TSet[T]) String() string {
|
||||
if set == nil {
|
||||
return ""
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
var (
|
||||
s string
|
||||
l = len(set.data)
|
||||
i = 0
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
buffer.WriteByte('[')
|
||||
for k := range set.data {
|
||||
s = gconv.String(k)
|
||||
if gstr.IsNumeric(s) {
|
||||
buffer.WriteString(s)
|
||||
} else {
|
||||
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
|
||||
}
|
||||
if i != l-1 {
|
||||
buffer.WriteByte(',')
|
||||
}
|
||||
i++
|
||||
}
|
||||
buffer.WriteByte(']')
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with callback function `f`.
|
||||
func (set *TSet[T]) LockFunc(f func(m map[T]struct{})) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
f(set.data)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with callback function `f`.
|
||||
func (set *TSet[T]) RLockFunc(f func(m map[T]struct{})) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
f(set.data)
|
||||
}
|
||||
|
||||
// Equal checks whether the two sets equal.
|
||||
func (set *TSet[T]) Equal(other *TSet[T]) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
if len(set.data) != len(other.data) {
|
||||
return false
|
||||
}
|
||||
for key := range set.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSubsetOf checks whether the current set is a sub-set of `other`.
|
||||
func (set *TSet[T]) IsSubsetOf(other *TSet[T]) bool {
|
||||
if set == other {
|
||||
return true
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key := range set.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Union returns a new set which is the union of `set` and `others`.
|
||||
// Which means, all the items in `newSet` are in `set` or in `others`.
|
||||
func (set *TSet[T]) Union(others ...*TSet[T]) (newSet *TSet[T]) {
|
||||
newSet = NewTSet[T]()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if other == nil {
|
||||
continue
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.data {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
if set != other {
|
||||
for k, v := range other.data {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Diff returns a new set which is the difference set from `set` to `others`.
|
||||
// Which means, all the items in `newSet` are in `set` but not in `others`.
|
||||
func (set *TSet[T]) Diff(others ...*TSet[T]) (newSet *TSet[T]) {
|
||||
newSet = NewTSet[T]()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if other == nil {
|
||||
continue
|
||||
}
|
||||
if set == other {
|
||||
continue
|
||||
}
|
||||
other.mu.RLock()
|
||||
for k, v := range set.data {
|
||||
if _, ok := other.data[k]; !ok {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Intersect returns a new set which is the intersection from `set` to `others`.
|
||||
// Which means, all the items in `newSet` are in `set` and also in `others`.
|
||||
func (set *TSet[T]) Intersect(others ...*TSet[T]) (newSet *TSet[T]) {
|
||||
newSet = NewTSet[T]()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for _, other := range others {
|
||||
if other == nil {
|
||||
continue
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range set.data {
|
||||
if _, ok := other.data[k]; ok {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Complement returns a new set which is the complement from `set` to `full`.
|
||||
// Which means, all the items in `newSet` are in `full` and not in `set`.
|
||||
//
|
||||
// It returns the difference between `full` and `set`
|
||||
// if the given set `full` is not the full set of `set`.
|
||||
func (set *TSet[T]) Complement(full *TSet[T]) (newSet *TSet[T]) {
|
||||
newSet = NewTSet[T]()
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if set != full {
|
||||
full.mu.RLock()
|
||||
defer full.mu.RUnlock()
|
||||
}
|
||||
for k, v := range full.data {
|
||||
if _, ok := set.data[k]; !ok {
|
||||
newSet.data[k] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Merge adds items from `others` sets into `set`.
|
||||
func (set *TSet[T]) Merge(others ...*TSet[T]) *TSet[T] {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for _, other := range others {
|
||||
if other == nil {
|
||||
continue
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
set.data[k] = v
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// Sum sums items.
|
||||
// Note: The items should be converted to int type,
|
||||
// or you'd get a result that you unexpected.
|
||||
func (set *TSet[T]) Sum() (sum int) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k := range set.data {
|
||||
sum += gconv.Int(k)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pop randomly pops an item from set.
|
||||
func (set *TSet[T]) Pop() (item T) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for k := range set.data {
|
||||
delete(set.data, k)
|
||||
return k
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops randomly pops `size` items from set.
|
||||
// It returns all items if size == -1.
|
||||
func (set *TSet[T]) Pops(size int) []T {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if size > len(set.data) || size == -1 {
|
||||
size = len(set.data)
|
||||
}
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
index := 0
|
||||
array := make([]T, size)
|
||||
for k := range set.data {
|
||||
delete(set.data, k)
|
||||
array[index] = k
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// Walk applies a user supplied function `f` to every item of set.
|
||||
func (set *TSet[T]) Walk(f func(item T) T) *TSet[T] {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
m := make(map[T]struct{}, len(set.data))
|
||||
for k, v := range set.data {
|
||||
m[f(k)] = v
|
||||
}
|
||||
set.data = m
|
||||
return set
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (set TSet[T]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(set.Slice())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (set *TSet[T]) UnmarshalJSON(b []byte) error {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[T]struct{})
|
||||
}
|
||||
var array []T
|
||||
if err := json.UnmarshalUseNumber(b, &array); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range array {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for set.
|
||||
func (set *TSet[T]) UnmarshalValue(value any) (err error) {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
if set.data == nil {
|
||||
set.data = make(map[T]struct{})
|
||||
}
|
||||
var array []T
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
err = json.UnmarshalUseNumber(gconv.Bytes(value), &array)
|
||||
default:
|
||||
if err = gconv.Scan(value, &array); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, v := range array {
|
||||
set.data[v] = struct{}{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (set *TSet[T]) DeepCopy() any {
|
||||
if set == nil {
|
||||
return nil
|
||||
}
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
data := make([]T, 0)
|
||||
for k := range set.data {
|
||||
data = append(data, k)
|
||||
}
|
||||
return NewTSetFrom[T](data, set.mu.IsSafe())
|
||||
}
|
||||
@ -187,6 +187,19 @@ func TestSet_Union(t *testing.T) {
|
||||
t.Assert(s3.Contains(3), true)
|
||||
t.Assert(s3.Contains(4), true)
|
||||
})
|
||||
|
||||
// Test with nil element in slice
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewSet()
|
||||
s2 := gset.NewSet()
|
||||
s1.Add(1, 2)
|
||||
s2.Add(3, 4)
|
||||
s3 := s1.Union(s2, nil)
|
||||
t.Assert(s3.Contains(1), true)
|
||||
t.Assert(s3.Contains(2), true)
|
||||
t.Assert(s3.Contains(3), true)
|
||||
t.Assert(s3.Contains(4), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Diff(t *testing.T) {
|
||||
@ -236,6 +249,14 @@ func TestSet_Complement(t *testing.T) {
|
||||
t.Assert(s3.Contains(4), true)
|
||||
t.Assert(s3.Contains(5), true)
|
||||
})
|
||||
|
||||
// Test with nil full set
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewSet()
|
||||
s1.Add(1, 2, 3)
|
||||
s3 := s1.Complement(nil)
|
||||
t.Assert(s3.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewFrom(t *testing.T) {
|
||||
|
||||
@ -167,6 +167,19 @@ func TestIntSet_Union(t *testing.T) {
|
||||
t.Assert(s3.Contains(3), true)
|
||||
t.Assert(s3.Contains(4), true)
|
||||
})
|
||||
|
||||
// Test with nil element in slice
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewIntSet()
|
||||
s2 := gset.NewIntSet()
|
||||
s1.Add(1, 2)
|
||||
s2.Add(3, 4)
|
||||
s3 := s1.Union(s2, nil)
|
||||
t.Assert(s3.Contains(1), true)
|
||||
t.Assert(s3.Contains(2), true)
|
||||
t.Assert(s3.Contains(3), true)
|
||||
t.Assert(s3.Contains(4), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Diff(t *testing.T) {
|
||||
@ -216,6 +229,14 @@ func TestIntSet_Complement(t *testing.T) {
|
||||
t.Assert(s3.Contains(4), true)
|
||||
t.Assert(s3.Contains(5), true)
|
||||
})
|
||||
|
||||
// Test with nil full set
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewIntSet()
|
||||
s1.Add(1, 2, 3)
|
||||
s3 := s1.Complement(nil)
|
||||
t.Assert(s3.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntSet_Size(t *testing.T) {
|
||||
|
||||
@ -178,6 +178,19 @@ func TestStrSet_Union(t *testing.T) {
|
||||
t.Assert(s3.Contains("3"), true)
|
||||
t.Assert(s3.Contains("4"), true)
|
||||
})
|
||||
|
||||
// Test with nil element in slice
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewStrSet()
|
||||
s2 := gset.NewStrSet()
|
||||
s1.Add("1", "2")
|
||||
s2.Add("3", "4")
|
||||
s3 := s1.Union(s2, nil)
|
||||
t.Assert(s3.Contains("1"), true)
|
||||
t.Assert(s3.Contains("2"), true)
|
||||
t.Assert(s3.Contains("3"), true)
|
||||
t.Assert(s3.Contains("4"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrSet_Diff(t *testing.T) {
|
||||
@ -227,6 +240,14 @@ func TestStrSet_Complement(t *testing.T) {
|
||||
t.Assert(s3.Contains("4"), true)
|
||||
t.Assert(s3.Contains("5"), true)
|
||||
})
|
||||
|
||||
// Test with nil full set
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewStrSet()
|
||||
s1.Add("1", "2", "3")
|
||||
s3 := s1.Complement(nil)
|
||||
t.Assert(s3.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewIntSetFrom(t *testing.T) {
|
||||
|
||||
593
container/gset/gset_z_unit_t_set_test.go
Normal file
593
container/gset/gset_z_unit_t_set_test.go
Normal file
@ -0,0 +1,593 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func TestTSet_New(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
s.Add(1, 1, 2)
|
||||
s.Add([]int{3, 4}...)
|
||||
t.Assert(s.Size(), 4)
|
||||
t.AssertIN(1, s.Slice())
|
||||
t.AssertIN(2, s.Slice())
|
||||
t.AssertIN(3, s.Slice())
|
||||
t.AssertIN(4, s.Slice())
|
||||
t.AssertNI(0, s.Slice())
|
||||
t.Assert(s.Contains(4), true)
|
||||
t.Assert(s.Contains(5), false)
|
||||
s.Remove(1)
|
||||
t.Assert(s.Size(), 3)
|
||||
s.Clear()
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_NewFrom(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3}, true)
|
||||
t.Assert(s.Size(), 3)
|
||||
t.Assert(s.Contains(1), true)
|
||||
t.Assert(s.Contains(2), true)
|
||||
t.Assert(s.Contains(3), true)
|
||||
t.Assert(s.Contains(4), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Add_NilData(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s gset.TSet[int]
|
||||
s.Add(1, 2, 3)
|
||||
t.Assert(s.Size(), 3)
|
||||
t.Assert(s.Contains(1), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_AddIfNotExist(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int](true)
|
||||
s.Add(1)
|
||||
t.Assert(s.Contains(1), true)
|
||||
t.Assert(s.AddIfNotExist(1), false)
|
||||
t.Assert(s.AddIfNotExist(2), true)
|
||||
t.Assert(s.Contains(2), true)
|
||||
t.Assert(s.AddIfNotExist(2), false)
|
||||
})
|
||||
|
||||
// Test with pointer type to test nil check
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[*int](true)
|
||||
|
||||
val := 1
|
||||
ptr := &val
|
||||
t.Assert(s.AddIfNotExist(ptr), true)
|
||||
t.Assert(s.AddIfNotExist(ptr), false)
|
||||
})
|
||||
|
||||
// Test nil data map initialization
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s gset.TSet[int]
|
||||
t.Assert(s.AddIfNotExist(1), true)
|
||||
t.Assert(s.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_AddIfNotExistFunc(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int](true)
|
||||
s.Add(1)
|
||||
t.Assert(s.Contains(1), true)
|
||||
t.Assert(s.Contains(2), false)
|
||||
t.Assert(s.AddIfNotExistFunc(2, func() bool { return false }), false)
|
||||
t.Assert(s.Contains(2), false)
|
||||
t.Assert(s.AddIfNotExistFunc(2, func() bool { return true }), true)
|
||||
t.Assert(s.Contains(2), true)
|
||||
t.Assert(s.AddIfNotExistFunc(2, func() bool { return true }), false)
|
||||
t.Assert(s.Contains(2), true)
|
||||
})
|
||||
|
||||
// Test concurrent scenario
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int](true)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
r := s.AddIfNotExistFunc(1, func() bool {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return true
|
||||
})
|
||||
t.Assert(r, false)
|
||||
}()
|
||||
s.Add(1)
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
// Test nil data map initialization
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s gset.TSet[int]
|
||||
t.Assert(s.AddIfNotExistFunc(1, func() bool { return true }), true)
|
||||
t.Assert(s.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_AddIfNotExistFuncLock(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int](true)
|
||||
s.Add(1)
|
||||
t.Assert(s.Contains(1), true)
|
||||
t.Assert(s.Contains(2), false)
|
||||
t.Assert(s.AddIfNotExistFuncLock(2, func() bool { return false }), false)
|
||||
t.Assert(s.Contains(2), false)
|
||||
t.Assert(s.AddIfNotExistFuncLock(2, func() bool { return true }), true)
|
||||
t.Assert(s.Contains(2), true)
|
||||
t.Assert(s.AddIfNotExistFuncLock(2, func() bool { return true }), false)
|
||||
t.Assert(s.Contains(2), true)
|
||||
})
|
||||
|
||||
// Test nil data map initialization
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s gset.TSet[int]
|
||||
t.Assert(s.AddIfNotExistFuncLock(1, func() bool { return true }), true)
|
||||
t.Assert(s.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Iterator(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3, 4, 5}, true)
|
||||
var sum int
|
||||
s.Iterator(func(v int) bool {
|
||||
sum += v
|
||||
return true
|
||||
})
|
||||
t.Assert(sum, 15)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3, 4, 5}, true)
|
||||
var count int
|
||||
s.Iterator(func(v int) bool {
|
||||
count++
|
||||
return count < 3
|
||||
})
|
||||
t.Assert(count, 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Join(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
t.Assert(s.Join(","), "")
|
||||
s.Add(1, 2, 3)
|
||||
result := s.Join(",")
|
||||
t.Assert(len(result) > 0, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_String(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s *gset.TSet[int]
|
||||
t.Assert(s.String(), "")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
t.Assert(s.String(), "[]")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
result := s.String()
|
||||
t.Assert(len(result) > 2, true)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[string]([]string{"a", "b", "c"})
|
||||
result := s.String()
|
||||
t.Assert(len(result) > 2, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Equal(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s2 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
t.Assert(s1.Equal(s2), true)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s2 := gset.NewTSetFrom[int]([]int{1, 2, 3, 4})
|
||||
t.Assert(s1.Equal(s2), false)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
t.Assert(s1.Equal(s1), true)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s2 := gset.NewTSetFrom[int]([]int{1, 2, 4})
|
||||
t.Assert(s1.Equal(s2), false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_IsSubsetOf(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2})
|
||||
s2 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
t.Assert(s1.IsSubsetOf(s2), true)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s2 := gset.NewTSetFrom[int]([]int{1, 2})
|
||||
t.Assert(s1.IsSubsetOf(s2), false)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
t.Assert(s1.IsSubsetOf(s1), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Union(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s2 := gset.NewTSetFrom[int]([]int{3, 4, 5})
|
||||
s := s1.Union(s2)
|
||||
t.Assert(s.Size(), 5)
|
||||
t.Assert(s.Contains(1), true)
|
||||
t.Assert(s.Contains(2), true)
|
||||
t.Assert(s.Contains(3), true)
|
||||
t.Assert(s.Contains(4), true)
|
||||
t.Assert(s.Contains(5), true)
|
||||
})
|
||||
|
||||
// Test with nil set - should skip it and copy s1 data
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
var s2 *gset.TSet[int]
|
||||
s := s1.Union(s2)
|
||||
// Since s2 is nil and skipped, newSet will be empty
|
||||
// because the loop runs but nothing is copied when other is nil
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
|
||||
// Test with self
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s := s1.Union(s1)
|
||||
t.Assert(s.Size(), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Diff(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s2 := gset.NewTSetFrom[int]([]int{3, 4, 5})
|
||||
s := s1.Diff(s2)
|
||||
t.Assert(s.Size(), 2)
|
||||
t.Assert(s.Contains(1), true)
|
||||
t.Assert(s.Contains(2), true)
|
||||
t.Assert(s.Contains(3), false)
|
||||
})
|
||||
|
||||
// Test with nil set - should skip it
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
var s2 *gset.TSet[int]
|
||||
s := s1.Diff(s2)
|
||||
// Since s2 is nil and skipped, newSet will be empty
|
||||
// because the loop runs but nothing is copied when other is nil
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
|
||||
// Test with self
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s := s1.Diff(s1)
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Intersect(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s2 := gset.NewTSetFrom[int]([]int{3, 4, 5})
|
||||
s := s1.Intersect(s2)
|
||||
t.Assert(s.Size(), 1)
|
||||
t.Assert(s.Contains(3), true)
|
||||
})
|
||||
|
||||
// Test with nil set
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
var s2 *gset.TSet[int]
|
||||
s := s1.Intersect(s2)
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
|
||||
// Test with self
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s := s1.Intersect(s1)
|
||||
t.Assert(s.Size(), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Complement(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s2 := gset.NewTSetFrom[int]([]int{1, 2, 3, 4, 5})
|
||||
s := s1.Complement(s2)
|
||||
t.Assert(s.Size(), 2)
|
||||
t.Assert(s.Contains(4), true)
|
||||
t.Assert(s.Contains(5), true)
|
||||
})
|
||||
|
||||
// Test with self
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s := s1.Complement(s1)
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Merge(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s2 := gset.NewTSetFrom[int]([]int{3, 4, 5})
|
||||
s1.Merge(s2)
|
||||
t.Assert(s1.Size(), 5)
|
||||
t.Assert(s1.Contains(1), true)
|
||||
t.Assert(s1.Contains(2), true)
|
||||
t.Assert(s1.Contains(3), true)
|
||||
t.Assert(s1.Contains(4), true)
|
||||
t.Assert(s1.Contains(5), true)
|
||||
})
|
||||
|
||||
// Test with nil set
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
var s2 *gset.TSet[int]
|
||||
s1.Merge(s2)
|
||||
t.Assert(s1.Size(), 3)
|
||||
})
|
||||
|
||||
// Test with self
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
s1.Merge(s1)
|
||||
t.Assert(s1.Size(), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Sum(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
t.Assert(s.Sum(), 6)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Pop(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
item := s.Pop()
|
||||
t.Assert(s.Size(), 2)
|
||||
t.Assert(s.Contains(item), false)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
item := s.Pop()
|
||||
t.Assert(item, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Pops(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3, 4, 5})
|
||||
items := s.Pops(3)
|
||||
t.Assert(len(items), 3)
|
||||
t.Assert(s.Size(), 2)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
items := s.Pops(-1)
|
||||
t.Assert(len(items), 3)
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
items := s.Pops(0)
|
||||
t.Assert(items, nil)
|
||||
t.Assert(s.Size(), 3)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
items := s.Pops(10)
|
||||
t.Assert(len(items), 3)
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_Walk(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2})
|
||||
s.Walk(func(item int) int {
|
||||
return item + 10
|
||||
})
|
||||
t.Assert(s.Size(), 2)
|
||||
t.Assert(s.Contains(11), true)
|
||||
t.Assert(s.Contains(12), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_MarshalJSON(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3})
|
||||
b, err := json.Marshal(s)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(b) > 0, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_UnmarshalJSON(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
b := []byte(`[1,2,3]`)
|
||||
err := json.UnmarshalUseNumber(b, &s)
|
||||
t.AssertNil(err)
|
||||
t.Assert(s.Size(), 3)
|
||||
t.Assert(s.Contains(1), true)
|
||||
t.Assert(s.Contains(2), true)
|
||||
t.Assert(s.Contains(3), true)
|
||||
})
|
||||
|
||||
// Test with nil data map
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s gset.TSet[int]
|
||||
b := []byte(`[1,2,3]`)
|
||||
err := json.UnmarshalUseNumber(b, &s)
|
||||
t.AssertNil(err)
|
||||
t.Assert(s.Size(), 3)
|
||||
})
|
||||
|
||||
// Test with invalid JSON
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
b := []byte(`{invalid}`)
|
||||
err := json.UnmarshalUseNumber(b, &s)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
|
||||
// Test with empty array
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
b := []byte(`[]`)
|
||||
err := json.UnmarshalUseNumber(b, &s)
|
||||
t.AssertNil(err)
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_UnmarshalValue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
err := s.UnmarshalValue([]byte(`[1,2,3]`))
|
||||
t.AssertNil(err)
|
||||
t.Assert(s.Size(), 3)
|
||||
t.Assert(s.Contains(1), true)
|
||||
t.Assert(s.Contains(2), true)
|
||||
t.Assert(s.Contains(3), true)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
err := s.UnmarshalValue(`[1,2,3]`)
|
||||
t.AssertNil(err)
|
||||
t.Assert(s.Size(), 3)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
err := s.UnmarshalValue([]int{1, 2, 3})
|
||||
t.AssertNil(err)
|
||||
t.Assert(s.Size(), 3)
|
||||
})
|
||||
|
||||
// Test with nil data map
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s gset.TSet[int]
|
||||
err := s.UnmarshalValue([]int{1, 2, 3})
|
||||
t.AssertNil(err)
|
||||
t.Assert(s.Size(), 3)
|
||||
})
|
||||
|
||||
// Test error case with invalid JSON
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
err := s.UnmarshalValue([]byte(`{invalid}`))
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
|
||||
// Test with empty array for string/bytes case
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
err := s.UnmarshalValue([]byte(`[]`))
|
||||
t.AssertNil(err)
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
|
||||
// Test with empty slice for default case
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSet[int]()
|
||||
err := s.UnmarshalValue([]int{})
|
||||
t.AssertNil(err)
|
||||
t.Assert(s.Size(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_DeepCopy(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := gset.NewTSetFrom[int]([]int{1, 2, 3}, true)
|
||||
s2 := s1.DeepCopy().(*gset.TSet[int])
|
||||
t.Assert(s1.Size(), s2.Size())
|
||||
t.Assert(s1.Contains(1), s2.Contains(1))
|
||||
t.Assert(s1.Contains(2), s2.Contains(2))
|
||||
t.Assert(s1.Contains(3), s2.Contains(3))
|
||||
|
||||
s1.Add(4)
|
||||
t.Assert(s1.Size(), 4)
|
||||
t.Assert(s2.Size(), 3)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s1 *gset.TSet[int]
|
||||
s2 := s1.DeepCopy()
|
||||
t.Assert(s2, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_LockFunc(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3}, true)
|
||||
s.LockFunc(func(m map[int]struct{}) {
|
||||
m[4] = struct{}{}
|
||||
})
|
||||
t.Assert(s.Size(), 4)
|
||||
t.Assert(s.Contains(4), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTSet_RLockFunc(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := gset.NewTSetFrom[int]([]int{1, 2, 3}, true)
|
||||
var sum int
|
||||
s.RLockFunc(func(m map[int]struct{}) {
|
||||
for k := range m {
|
||||
sum += k
|
||||
}
|
||||
})
|
||||
t.Assert(sum, 6)
|
||||
})
|
||||
}
|
||||
@ -162,12 +162,12 @@ type iTree interface {
|
||||
IteratorDescFrom(key any, match bool, f func(key, value any) bool)
|
||||
}
|
||||
|
||||
// iteratorFromGetIndex returns the index of the key in the keys slice.
|
||||
// iteratorFromGetIndexT returns the index of the key in the keys slice.
|
||||
//
|
||||
// The parameter `match` specifies whether starting iterating only if the `key` is fully matched,
|
||||
// or else using index searching iterating.
|
||||
// If `isIterator` is true, iterator is available; or else not.
|
||||
func iteratorFromGetIndex(key any, keys []any, match bool) (index int, canIterator bool) {
|
||||
func iteratorFromGetIndexT[T comparable](key T, keys []T, match bool) (index int, canIterator bool) {
|
||||
if match {
|
||||
for i, k := range keys {
|
||||
if k == key {
|
||||
@ -176,10 +176,19 @@ func iteratorFromGetIndex(key any, keys []any, match bool) (index int, canIterat
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if i, ok := key.(int); ok {
|
||||
if i, ok := any(key).(int); ok {
|
||||
canIterator = true
|
||||
index = i
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// iteratorFromGetIndex returns the index of the key in the keys slice.
|
||||
//
|
||||
// The parameter `match` specifies whether starting iterating only if the `key` is fully matched,
|
||||
// or else using index searching iterating.
|
||||
// If `isIterator` is true, iterator is available; or else not.
|
||||
func iteratorFromGetIndex(key any, keys []any, match bool) (index int, canIterator bool) {
|
||||
return iteratorFromGetIndexT(key, keys, match)
|
||||
}
|
||||
|
||||
@ -7,31 +7,22 @@
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emirpasic/gods/trees/avltree"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var _ iTree = (*AVLTree)(nil)
|
||||
|
||||
// AVLTree holds elements of the AVL tree.
|
||||
type AVLTree struct {
|
||||
mu rwmutex.RWMutex
|
||||
root *AVLTreeNode
|
||||
comparator func(v1, v2 any) int
|
||||
tree *avltree.Tree
|
||||
*AVLKVTree[any, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// AVLTreeNode is a single element within the tree.
|
||||
type AVLTreeNode struct {
|
||||
Key any
|
||||
Value any
|
||||
}
|
||||
type AVLTreeNode = AVLKVTreeNode[any, any]
|
||||
|
||||
// NewAVLTree instantiates an AVL tree with the custom key comparator.
|
||||
//
|
||||
@ -39,9 +30,7 @@ type AVLTreeNode struct {
|
||||
// which is false in default.
|
||||
func NewAVLTree(comparator func(v1, v2 any) int, safe ...bool) *AVLTree {
|
||||
return &AVLTree{
|
||||
mu: rwmutex.Create(safe...),
|
||||
comparator: comparator,
|
||||
tree: avltree.NewWith(comparator),
|
||||
AVLKVTree: NewAVLKVTree[any, any](comparator, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,58 +38,55 @@ func NewAVLTree(comparator func(v1, v2 any) int, safe ...bool) *AVLTree {
|
||||
//
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety, which is false in default.
|
||||
func NewAVLTreeFrom(comparator func(v1, v2 any) int, data map[any]any, safe ...bool) *AVLTree {
|
||||
tree := NewAVLTree(comparator, safe...)
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
return &AVLTree{
|
||||
AVLKVTree: NewAVLKVTreeFrom(comparator, data, safe...),
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the tree.
|
||||
func (tree *AVLTree) lazyInit() {
|
||||
tree.once.Do(func() {
|
||||
if tree.AVLKVTree == nil {
|
||||
tree.AVLKVTree = NewAVLKVTree[any, any](gutil.ComparatorTStr, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Clone clones and returns a new tree from current tree.
|
||||
func (tree *AVLTree) Clone() *AVLTree {
|
||||
newTree := NewAVLTree(tree.comparator, tree.mu.IsSafe())
|
||||
newTree.Sets(tree.Map())
|
||||
return newTree
|
||||
if tree == nil {
|
||||
return nil
|
||||
}
|
||||
tree.lazyInit()
|
||||
return &AVLTree{
|
||||
AVLKVTree: tree.AVLKVTree.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets key-value pair into the tree.
|
||||
func (tree *AVLTree) Set(key any, value any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.doSet(key, value)
|
||||
tree.lazyInit()
|
||||
tree.AVLKVTree.Set(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the tree.
|
||||
func (tree *AVLTree) Sets(data map[any]any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for key, value := range data {
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.AVLKVTree.Sets(data)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *AVLTree) SetIfNotExist(key any, value any) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *AVLTree) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -109,13 +95,8 @@ func (tree *AVLTree) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` within mutex lock.
|
||||
func (tree *AVLTree) SetIfNotExistFuncLock(key any, f func() any) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Get searches the `key` in the tree and returns its associated `value` or nil if key is not found in tree.
|
||||
@ -123,32 +104,22 @@ func (tree *AVLTree) SetIfNotExistFuncLock(key any, f func() any) bool {
|
||||
// Note that, the `nil` value from Get function cannot be used to determine key existence, please use Contains function
|
||||
// to do so.
|
||||
func (tree *AVLTree) Get(key any) (value any) {
|
||||
value, _ = tree.Search(key)
|
||||
return
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Get(key)
|
||||
}
|
||||
|
||||
// GetOrSet returns its `value` of `key`, or sets value with given `value` if it does not exist and then returns
|
||||
// this value.
|
||||
func (tree *AVLTree) GetOrSet(key any, value any) any {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns its `value` of `key`, or sets value with returned value of callback function `f` if it does not
|
||||
// exist and then returns this value.
|
||||
func (tree *AVLTree) GetOrSetFunc(key any, f func() any) any {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns its `value` of `key`, or sets value with returned value of callback function `f` if it does
|
||||
@ -156,13 +127,8 @@ func (tree *AVLTree) GetOrSetFunc(key any, f func() any) any {
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` within mutex lock.
|
||||
func (tree *AVLTree) GetOrSetFuncLock(key any, f func() any) any {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given `key`.
|
||||
@ -170,7 +136,8 @@ func (tree *AVLTree) GetOrSetFuncLock(key any, f func() any) any {
|
||||
//
|
||||
// Also see function Get.
|
||||
func (tree *AVLTree) GetVar(key any) *gvar.Var {
|
||||
return gvar.New(tree.Get(key))
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
@ -178,7 +145,8 @@ func (tree *AVLTree) GetVar(key any) *gvar.Var {
|
||||
//
|
||||
// Also see function GetOrSet.
|
||||
func (tree *AVLTree) GetVarOrSet(key any, value any) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSet(key, value))
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
@ -186,7 +154,8 @@ func (tree *AVLTree) GetVarOrSet(key any, value any) *gvar.Var {
|
||||
//
|
||||
// Also see function GetOrSetFunc.
|
||||
func (tree *AVLTree) GetVarOrSetFunc(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFunc(key, f))
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
@ -194,127 +163,100 @@ func (tree *AVLTree) GetVarOrSetFunc(key any, f func() any) *gvar.Var {
|
||||
//
|
||||
// Also see function GetOrSetFuncLock.
|
||||
func (tree *AVLTree) GetVarOrSetFuncLock(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFuncLock(key, f))
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Search searches the tree with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (tree *AVLTree) Search(key any) (value any, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
if node, found := tree.doGet(key); found {
|
||||
return node, true
|
||||
}
|
||||
return nil, false
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Search(key)
|
||||
}
|
||||
|
||||
// Contains checks and returns whether given `key` exists in the tree.
|
||||
func (tree *AVLTree) Contains(key any) bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
_, ok := tree.doGet(key)
|
||||
return ok
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns number of nodes in the tree.
|
||||
func (tree *AVLTree) Size() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size()
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Size()
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the tree does not contain any nodes.
|
||||
func (tree *AVLTree) IsEmpty() bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size() == 0
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.IsEmpty()
|
||||
}
|
||||
|
||||
// Remove removes the node from the tree by `key`, and returns its associated value of `key`.
|
||||
// The given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLTree) Remove(key any) (value any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
return tree.doRemove(key)
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Remove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes key-value pairs from the tree by `keys`.
|
||||
func (tree *AVLTree) Removes(keys []any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
tree.doRemove(key)
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.AVLKVTree.Removes(keys)
|
||||
}
|
||||
|
||||
// Clear removes all nodes from the tree.
|
||||
func (tree *AVLTree) Clear() {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
tree.lazyInit()
|
||||
tree.AVLKVTree.Clear()
|
||||
}
|
||||
|
||||
// Keys returns all keys from the tree in order by its comparator.
|
||||
func (tree *AVLTree) Keys() []any {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Keys()
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values from the true in order by its comparator based on the key.
|
||||
func (tree *AVLTree) Values() []any {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Values()
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Values()
|
||||
}
|
||||
|
||||
// Replace clears the data of the tree and sets the nodes by given `data`.
|
||||
func (tree *AVLTree) Replace(data map[any]any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.AVLKVTree.Replace(data)
|
||||
}
|
||||
|
||||
// Print prints the tree to stdout.
|
||||
func (tree *AVLTree) Print() {
|
||||
fmt.Println(tree.String())
|
||||
tree.lazyInit()
|
||||
tree.AVLKVTree.Print()
|
||||
}
|
||||
|
||||
// String returns a string representation of container.
|
||||
func (tree *AVLTree) String() string {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return gstr.Replace(tree.tree.String(), "AVLTree\n", "")
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (tree *AVLTree) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.MarshalJSON()
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.MarshalJSON()
|
||||
}
|
||||
|
||||
// Map returns all key-value pairs as map.
|
||||
func (tree *AVLTree) Map() map[any]any {
|
||||
m := make(map[any]any, tree.Size())
|
||||
tree.IteratorAsc(func(key, value any) bool {
|
||||
m[key] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns all key-value items as map[string]any.
|
||||
func (tree *AVLTree) MapStrAny() map[string]any {
|
||||
m := make(map[string]any, tree.Size())
|
||||
tree.IteratorAsc(func(key, value any) bool {
|
||||
m[gconv.String(key)] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.MapStrAny()
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
@ -334,18 +276,8 @@ func (tree *AVLTree) IteratorFrom(key any, match bool, f func(key, value any) bo
|
||||
// IteratorAsc iterates the tree readonly in ascending order with given callback function `f`.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *AVLTree) IteratorAsc(f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.Begin(); it.Next(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.AVLKVTree.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function `f`.
|
||||
@ -355,34 +287,16 @@ func (tree *AVLTree) IteratorAsc(f func(key, value any) bool) {
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *AVLTree) IteratorAscFrom(key any, match bool, f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndex(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index < len(keys); index++ {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.AVLKVTree.IteratorAscFrom(key, match, f)
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the tree readonly in descending order with given callback function `f`.
|
||||
//
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *AVLTree) IteratorDesc(f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.End(); it.Prev(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.AVLKVTree.IteratorDesc(f)
|
||||
}
|
||||
|
||||
// IteratorDescFrom iterates the tree readonly in descending order with given callback function `f`.
|
||||
@ -392,44 +306,20 @@ func (tree *AVLTree) IteratorDesc(f func(key, value any) bool) {
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *AVLTree) IteratorDescFrom(key any, match bool, f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndex(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index >= 0; index-- {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.AVLKVTree.IteratorDescFrom(key, match, f)
|
||||
}
|
||||
|
||||
// Left returns the minimum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *AVLTree) Left() *AVLTreeNode {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Left()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return &AVLTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Left()
|
||||
}
|
||||
|
||||
// Right returns the maximum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *AVLTree) Right() *AVLTreeNode {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Right()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return &AVLTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Right()
|
||||
}
|
||||
|
||||
// Floor Finds floor node of the input key, returns the floor node or nil if no floor node is found.
|
||||
@ -441,16 +331,8 @@ func (tree *AVLTree) Right() *AVLTreeNode {
|
||||
//
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLTree) Floor(key any) (floor *AVLTreeNode, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node, ok := tree.tree.Floor(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return &AVLTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}, true
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Floor(key)
|
||||
}
|
||||
|
||||
// Ceiling finds ceiling node of the input key, returns the ceiling node or nil if no ceiling node is found.
|
||||
@ -462,16 +344,8 @@ func (tree *AVLTree) Floor(key any) (floor *AVLTreeNode, found bool) {
|
||||
//
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLTree) Ceiling(key any) (ceiling *AVLTreeNode, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node, ok := tree.tree.Ceiling(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return &AVLTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}, true
|
||||
tree.lazyInit()
|
||||
return tree.AVLKVTree.Ceiling(key)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the tree to value-key.
|
||||
@ -480,6 +354,8 @@ func (tree *AVLTree) Ceiling(key any) (ceiling *AVLTreeNode, found bool) {
|
||||
//
|
||||
// If the type of value is different with key, you pass the new `comparator`.
|
||||
func (tree *AVLTree) Flip(comparator ...func(v1, v2 any) int) {
|
||||
tree.lazyInit()
|
||||
|
||||
var t = new(AVLTree)
|
||||
if len(comparator) > 0 {
|
||||
t = NewAVLTree(comparator[0], tree.mu.IsSafe())
|
||||
@ -493,32 +369,3 @@ func (tree *AVLTree) Flip(comparator ...func(v1, v2 any) int) {
|
||||
tree.Clear()
|
||||
tree.Sets(t.Map())
|
||||
}
|
||||
|
||||
// doSet inserts key-value pair node into the tree without lock.
|
||||
// If `key` already exists, then its value is updated with the new value.
|
||||
// If `value` is type of <func() any>, it will be executed and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (tree *AVLTree) doSet(key, value any) any {
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value == nil {
|
||||
return value
|
||||
}
|
||||
tree.tree.Put(key, value)
|
||||
return value
|
||||
}
|
||||
|
||||
// doGet retrieves and returns the value of given key from tree without lock.
|
||||
func (tree *AVLTree) doGet(key any) (value any, found bool) {
|
||||
return tree.tree.Get(key)
|
||||
}
|
||||
|
||||
// doRemove removes key from tree and returns its associated value without lock.
|
||||
// Note that, the given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLTree) doRemove(key any) (value any) {
|
||||
value, _ = tree.tree.Get(key)
|
||||
tree.tree.Remove(key)
|
||||
return
|
||||
}
|
||||
|
||||
@ -7,31 +7,22 @@
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emirpasic/gods/trees/btree"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
var _ iTree = (*BTree)(nil)
|
||||
|
||||
// BTree holds elements of the B-tree.
|
||||
type BTree struct {
|
||||
mu rwmutex.RWMutex
|
||||
comparator func(v1, v2 any) int
|
||||
m int // order (maximum number of children)
|
||||
tree *btree.Tree
|
||||
*BKVTree[any, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// BTreeEntry represents the key-value pair contained within nodes.
|
||||
type BTreeEntry struct {
|
||||
Key any
|
||||
Value any
|
||||
}
|
||||
type BTreeEntry = BKVTreeEntry[any, any]
|
||||
|
||||
// NewBTree instantiates a B-tree with `m` (maximum number of children) and a custom key comparator.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
@ -39,10 +30,7 @@ type BTreeEntry struct {
|
||||
// Note that the `m` must be greater or equal than 3, or else it panics.
|
||||
func NewBTree(m int, comparator func(v1, v2 any) int, safe ...bool) *BTree {
|
||||
return &BTree{
|
||||
mu: rwmutex.Create(safe...),
|
||||
m: m,
|
||||
comparator: comparator,
|
||||
tree: btree.NewWith(m, comparator),
|
||||
BKVTree: NewBKVTree[any, any](m, comparator, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,58 +38,55 @@ func NewBTree(m int, comparator func(v1, v2 any) int, safe ...bool) *BTree {
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewBTreeFrom(m int, comparator func(v1, v2 any) int, data map[any]any, safe ...bool) *BTree {
|
||||
tree := NewBTree(m, comparator, safe...)
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
return &BTree{
|
||||
BKVTree: NewBKVTreeFrom(m, comparator, data, safe...),
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the tree.
|
||||
func (tree *BTree) lazyInit() {
|
||||
tree.once.Do(func() {
|
||||
if tree.BKVTree == nil {
|
||||
tree.BKVTree = NewBKVTree[any, any](3, gutil.ComparatorTStr, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Clone clones and returns a new tree from current tree.
|
||||
func (tree *BTree) Clone() *BTree {
|
||||
newTree := NewBTree(tree.m, tree.comparator, tree.mu.IsSafe())
|
||||
newTree.Sets(tree.Map())
|
||||
return newTree
|
||||
if tree == nil {
|
||||
return nil
|
||||
}
|
||||
tree.lazyInit()
|
||||
return &BTree{
|
||||
BKVTree: tree.BKVTree.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets key-value pair into the tree.
|
||||
func (tree *BTree) Set(key any, value any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.doSet(key, value)
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.Set(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the tree.
|
||||
func (tree *BTree) Sets(data map[any]any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.Sets(data)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *BTree) SetIfNotExist(key any, value any) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *BTree) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -110,13 +95,8 @@ func (tree *BTree) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` within mutex lock.
|
||||
func (tree *BTree) SetIfNotExistFuncLock(key any, f func() any) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Get searches the `key` in the tree and returns its associated `value` or nil if key is not found in tree.
|
||||
@ -124,34 +104,22 @@ func (tree *BTree) SetIfNotExistFuncLock(key any, f func() any) bool {
|
||||
// Note that, the `nil` value from Get function cannot be used to determine key existence, please use Contains function
|
||||
// to do so.
|
||||
func (tree *BTree) Get(key any) (value any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
value, _ = tree.doGet(key)
|
||||
return
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Get(key)
|
||||
}
|
||||
|
||||
// GetOrSet returns its `value` of `key`, or sets value with given `value` if it does not exist and then returns
|
||||
// this value.
|
||||
func (tree *BTree) GetOrSet(key any, value any) any {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns its `value` of `key`, or sets value with returned value of callback function `f` if it does not
|
||||
// exist and then returns this value.
|
||||
func (tree *BTree) GetOrSetFunc(key any, f func() any) any {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns its `value` of `key`, or sets value with returned value of callback function `f` if it does
|
||||
@ -159,13 +127,8 @@ func (tree *BTree) GetOrSetFunc(key any, f func() any) any {
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` within mutex lock.
|
||||
func (tree *BTree) GetOrSetFuncLock(key any, f func() any) any {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given `key`.
|
||||
@ -173,7 +136,8 @@ func (tree *BTree) GetOrSetFuncLock(key any, f func() any) any {
|
||||
//
|
||||
// Also see function Get.
|
||||
func (tree *BTree) GetVar(key any) *gvar.Var {
|
||||
return gvar.New(tree.Get(key))
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
@ -181,7 +145,8 @@ func (tree *BTree) GetVar(key any) *gvar.Var {
|
||||
//
|
||||
// Also see function GetOrSet.
|
||||
func (tree *BTree) GetVarOrSet(key any, value any) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSet(key, value))
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
@ -189,7 +154,8 @@ func (tree *BTree) GetVarOrSet(key any, value any) *gvar.Var {
|
||||
//
|
||||
// Also see function GetOrSetFunc.
|
||||
func (tree *BTree) GetVarOrSetFunc(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFunc(key, f))
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
@ -197,155 +163,123 @@ func (tree *BTree) GetVarOrSetFunc(key any, f func() any) *gvar.Var {
|
||||
//
|
||||
// Also see function GetOrSetFuncLock.
|
||||
func (tree *BTree) GetVarOrSetFuncLock(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFuncLock(key, f))
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Search searches the tree with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (tree *BTree) Search(key any) (value any, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Get(key)
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Search(key)
|
||||
}
|
||||
|
||||
// Contains checks and returns whether given `key` exists in the tree.
|
||||
func (tree *BTree) Contains(key any) bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
_, ok := tree.doGet(key)
|
||||
return ok
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns number of nodes in the tree.
|
||||
func (tree *BTree) Size() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size()
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Size()
|
||||
}
|
||||
|
||||
// IsEmpty returns true if tree does not contain any nodes
|
||||
func (tree *BTree) IsEmpty() bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size() == 0
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.IsEmpty()
|
||||
}
|
||||
|
||||
// Remove removes the node from the tree by `key`, and returns its associated value of `key`.
|
||||
// The given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *BTree) Remove(key any) (value any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
return tree.doRemove(key)
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Remove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes key-value pairs from the tree by `keys`.
|
||||
func (tree *BTree) Removes(keys []any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
tree.doRemove(key)
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.Removes(keys)
|
||||
}
|
||||
|
||||
// Clear removes all nodes from the tree.
|
||||
func (tree *BTree) Clear() {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.Clear()
|
||||
}
|
||||
|
||||
// Keys returns all keys from the tree in order by its comparator.
|
||||
func (tree *BTree) Keys() []any {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Keys()
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values from the true in order by its comparator based on the key.
|
||||
func (tree *BTree) Values() []any {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Values()
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Values()
|
||||
}
|
||||
|
||||
// Replace clears the data of the tree and sets the nodes by given `data`.
|
||||
func (tree *BTree) Replace(data map[any]any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.Replace(data)
|
||||
}
|
||||
|
||||
// Map returns all key-value pairs as map.
|
||||
func (tree *BTree) Map() map[any]any {
|
||||
m := make(map[any]any, tree.Size())
|
||||
tree.IteratorAsc(func(key, value any) bool {
|
||||
m[key] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns all key-value items as map[string]any.
|
||||
func (tree *BTree) MapStrAny() map[string]any {
|
||||
m := make(map[string]any, tree.Size())
|
||||
tree.IteratorAsc(func(key, value any) bool {
|
||||
m[gconv.String(key)] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.MapStrAny()
|
||||
}
|
||||
|
||||
// Print prints the tree to stdout.
|
||||
func (tree *BTree) Print() {
|
||||
fmt.Println(tree.String())
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.Print()
|
||||
}
|
||||
|
||||
// String returns a string representation of container (for debugging purposes)
|
||||
func (tree *BTree) String() string {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return gstr.Replace(tree.tree.String(), "BTree\n", "")
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (tree *BTree) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.MarshalJSON()
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.MarshalJSON()
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
//
|
||||
// Also see IteratorAsc.
|
||||
func (tree *BTree) Iterator(f func(key, value any) bool) {
|
||||
tree.IteratorAsc(f)
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.Iterator(f)
|
||||
}
|
||||
|
||||
// IteratorFrom is alias of IteratorAscFrom.
|
||||
//
|
||||
// Also see IteratorAscFrom.
|
||||
func (tree *BTree) IteratorFrom(key any, match bool, f func(key, value any) bool) {
|
||||
tree.IteratorAscFrom(key, match, f)
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.IteratorFrom(key, match, f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the tree readonly in ascending order with given callback function `f`.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *BTree) IteratorAsc(f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.Begin(); it.Next(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function `f`.
|
||||
@ -355,34 +289,16 @@ func (tree *BTree) IteratorAsc(f func(key, value any) bool) {
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *BTree) IteratorAscFrom(key any, match bool, f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndex(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index < len(keys); index++ {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.IteratorAscFrom(key, match, f)
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the tree readonly in descending order with given callback function `f`.
|
||||
//
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *BTree) IteratorDesc(f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.End(); it.Prev(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.IteratorDesc(f)
|
||||
}
|
||||
|
||||
// IteratorDescFrom iterates the tree readonly in descending order with given callback function `f`.
|
||||
@ -392,78 +308,24 @@ func (tree *BTree) IteratorDesc(f func(key, value any) bool) {
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *BTree) IteratorDescFrom(key any, match bool, f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndex(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index >= 0; index-- {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.BKVTree.IteratorDescFrom(key, match, f)
|
||||
}
|
||||
|
||||
// Height returns the height of the tree.
|
||||
func (tree *BTree) Height() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Height()
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Height()
|
||||
}
|
||||
|
||||
// Left returns the minimum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *BTree) Left() *BTreeEntry {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Left()
|
||||
if node == nil || node.Entries == nil || len(node.Entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &BTreeEntry{
|
||||
Key: node.Entries[0].Key,
|
||||
Value: node.Entries[0].Value,
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Left()
|
||||
}
|
||||
|
||||
// Right returns the maximum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *BTree) Right() *BTreeEntry {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Right()
|
||||
if node == nil || node.Entries == nil || len(node.Entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &BTreeEntry{
|
||||
Key: node.Entries[len(node.Entries)-1].Key,
|
||||
Value: node.Entries[len(node.Entries)-1].Value,
|
||||
}
|
||||
}
|
||||
|
||||
// doSet inserts key-value pair node into the tree without lock.
|
||||
// If `key` already exists, then its value is updated with the new value.
|
||||
// If `value` is type of <func() any>, it will be executed and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (tree *BTree) doSet(key any, value any) any {
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value == nil {
|
||||
return value
|
||||
}
|
||||
tree.tree.Put(key, value)
|
||||
return value
|
||||
}
|
||||
|
||||
// doGet get the value from the tree by key without lock.
|
||||
func (tree *BTree) doGet(key any) (value any, ok bool) {
|
||||
return tree.tree.Get(key)
|
||||
}
|
||||
|
||||
// doRemove removes key from tree and returns its associated value without lock.
|
||||
// Note that, the given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *BTree) doRemove(key any) (value any) {
|
||||
value, _ = tree.tree.Get(key)
|
||||
tree.tree.Remove(key)
|
||||
return
|
||||
tree.lazyInit()
|
||||
return tree.BKVTree.Right()
|
||||
}
|
||||
|
||||
539
container/gtree/gtree_k_v_avltree.go
Normal file
539
container/gtree/gtree_k_v_avltree.go
Normal file
@ -0,0 +1,539 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emirpasic/gods/v2/trees/avltree"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// AVLKVTree holds elements of the AVL tree.
|
||||
type AVLKVTree[K comparable, V any] struct {
|
||||
mu rwmutex.RWMutex
|
||||
comparator func(v1, v2 K) int
|
||||
tree *avltree.Tree[K, V]
|
||||
}
|
||||
|
||||
// AVLKVTreeNode is a single element within the tree.
|
||||
type AVLKVTreeNode[K comparable, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
||||
|
||||
// NewAVLKVTree instantiates an AVL tree with the custom key comparator.
|
||||
//
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewAVLKVTree[K comparable, V any](comparator func(v1, v2 K) int, safe ...bool) *AVLKVTree[K, V] {
|
||||
return &AVLKVTree[K, V]{
|
||||
mu: rwmutex.Create(safe...),
|
||||
comparator: comparator,
|
||||
tree: avltree.NewWith[K, V](comparator),
|
||||
}
|
||||
}
|
||||
|
||||
// NewAVLKVTreeFrom instantiates an AVL tree with the custom key comparator and data map.
|
||||
//
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety, which is false in default.
|
||||
func NewAVLKVTreeFrom[K comparable, V any](comparator func(v1, v2 K) int, data map[K]V, safe ...bool) *AVLKVTree[K, V] {
|
||||
tree := NewAVLKVTree[K, V](comparator, safe...)
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// Clone clones and returns a new tree from current tree.
|
||||
func (tree *AVLKVTree[K, V]) Clone() *AVLKVTree[K, V] {
|
||||
if tree == nil {
|
||||
return nil
|
||||
}
|
||||
newTree := NewAVLKVTree[K, V](tree.comparator, tree.mu.IsSafe())
|
||||
newTree.Sets(tree.Map())
|
||||
return newTree
|
||||
}
|
||||
|
||||
// Set sets key-value pair into the tree.
|
||||
func (tree *AVLKVTree[K, V]) Set(key K, value V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the tree.
|
||||
func (tree *AVLKVTree[K, V]) Sets(data map[K]V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for key, value := range data {
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *AVLKVTree[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *AVLKVTree[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` within mutex lock.
|
||||
func (tree *AVLKVTree[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get searches the `key` in the tree and returns its associated `value` or nil if key is not found in tree.
|
||||
//
|
||||
// Note that, the `nil` value from Get function cannot be used to determine key existence, please use Contains function
|
||||
// to do so.
|
||||
func (tree *AVLKVTree[K, V]) Get(key K) (value V) {
|
||||
value, _ = tree.Search(key)
|
||||
return
|
||||
}
|
||||
|
||||
// GetOrSet returns its `value` of `key`, or sets value with given `value` if it does not exist and then returns
|
||||
// this value.
|
||||
func (tree *AVLKVTree[K, V]) GetOrSet(key K, value V) V {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns its `value` of `key`, or sets value with returned value of callback function `f` if it does not
|
||||
// exist and then returns this value.
|
||||
func (tree *AVLKVTree[K, V]) GetOrSetFunc(key K, f func() V) V {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns its `value` of `key`, or sets value with returned value of callback function `f` if it does
|
||||
// not exist and then returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` within mutex lock.
|
||||
func (tree *AVLKVTree[K, V]) GetOrSetFuncLock(key K, f func() V) V {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given `key`.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function Get.
|
||||
func (tree *AVLKVTree[K, V]) GetVar(key K) *gvar.Var {
|
||||
return gvar.New(tree.Get(key))
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function GetOrSet.
|
||||
func (tree *AVLKVTree[K, V]) GetVarOrSet(key K, value V) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSet(key, value))
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function GetOrSetFunc.
|
||||
func (tree *AVLKVTree[K, V]) GetVarOrSetFunc(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFunc(key, f))
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function GetOrSetFuncLock.
|
||||
func (tree *AVLKVTree[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFuncLock(key, f))
|
||||
}
|
||||
|
||||
// Search searches the tree with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (tree *AVLKVTree[K, V]) Search(key K) (value V, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
if node, found := tree.doGet(key); found {
|
||||
return node, true
|
||||
}
|
||||
found = false
|
||||
return
|
||||
}
|
||||
|
||||
// Contains checks and returns whether given `key` exists in the tree.
|
||||
func (tree *AVLKVTree[K, V]) Contains(key K) bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
_, ok := tree.doGet(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Size returns number of nodes in the tree.
|
||||
func (tree *AVLKVTree[K, V]) Size() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size()
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the tree does not contain any nodes.
|
||||
func (tree *AVLKVTree[K, V]) IsEmpty() bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size() == 0
|
||||
}
|
||||
|
||||
// Remove removes the node from the tree by `key`, and returns its associated value of `key`.
|
||||
// The given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLKVTree[K, V]) Remove(key K) (value V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
return tree.doRemove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes key-value pairs from the tree by `keys`.
|
||||
func (tree *AVLKVTree[K, V]) Removes(keys []K) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
tree.doRemove(key)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all nodes from the tree.
|
||||
func (tree *AVLKVTree[K, V]) Clear() {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
}
|
||||
|
||||
// Keys returns all keys from the tree in order by its comparator.
|
||||
func (tree *AVLKVTree[K, V]) Keys() []K {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values from the true in order by its comparator based on the key.
|
||||
func (tree *AVLKVTree[K, V]) Values() []V {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Values()
|
||||
}
|
||||
|
||||
// Replace clears the data of the tree and sets the nodes by given `data`.
|
||||
func (tree *AVLKVTree[K, V]) Replace(data map[K]V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Print prints the tree to stdout.
|
||||
func (tree *AVLKVTree[K, V]) Print() {
|
||||
fmt.Println(tree.String())
|
||||
}
|
||||
|
||||
// String returns a string representation of container.
|
||||
func (tree *AVLKVTree[K, V]) String() string {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return gstr.Replace(tree.tree.String(), "AVLTree\n", "")
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (tree *AVLKVTree[K, V]) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
|
||||
elements := make(map[string]V)
|
||||
it := tree.tree.Iterator()
|
||||
for it.Next() {
|
||||
elements[gconv.String(it.Key())] = it.Value()
|
||||
}
|
||||
return json.Marshal(&elements)
|
||||
}
|
||||
|
||||
// Map returns all key-value pairs as map.
|
||||
func (tree *AVLKVTree[K, V]) Map() map[K]V {
|
||||
m := make(map[K]V, tree.Size())
|
||||
tree.IteratorAsc(func(key K, value V) bool {
|
||||
m[key] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
// MapStrAny returns all key-value items as map[string]any.
|
||||
func (tree *AVLKVTree[K, V]) MapStrAny() map[string]any {
|
||||
m := make(map[string]any, tree.Size())
|
||||
tree.IteratorAsc(func(key K, value V) bool {
|
||||
m[gconv.String(key)] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
//
|
||||
// Also see IteratorAsc.
|
||||
func (tree *AVLKVTree[K, V]) Iterator(f func(key K, value V) bool) {
|
||||
tree.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorFrom is alias of IteratorAscFrom.
|
||||
//
|
||||
// Also see IteratorAscFrom.
|
||||
func (tree *AVLKVTree[K, V]) IteratorFrom(key K, match bool, f func(key K, value V) bool) {
|
||||
tree.IteratorAscFrom(key, match, f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the tree readonly in ascending order with given callback function `f`.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *AVLKVTree[K, V]) IteratorAsc(f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.Begin(); it.Next(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function `f`.
|
||||
//
|
||||
// The parameter `key` specifies the start entry for iterating.
|
||||
// The parameter `match` specifies whether starting iterating only if the `key` is fully matched, or else using index
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *AVLKVTree[K, V]) IteratorAscFrom(key K, match bool, f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndexT(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index < len(keys); index++ {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the tree readonly in descending order with given callback function `f`.
|
||||
//
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *AVLKVTree[K, V]) IteratorDesc(f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.End(); it.Prev(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDescFrom iterates the tree readonly in descending order with given callback function `f`.
|
||||
//
|
||||
// The parameter `key` specifies the start entry for iterating.
|
||||
// The parameter `match` specifies whether starting iterating only if the `key` is fully matched, or else using index
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *AVLKVTree[K, V]) IteratorDescFrom(key K, match bool, f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndexT(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index >= 0; index-- {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
}
|
||||
|
||||
// Left returns the minimum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *AVLKVTree[K, V]) Left() *AVLKVTreeNode[K, V] {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Left()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return &AVLKVTreeNode[K, V]{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
}
|
||||
|
||||
// Right returns the maximum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *AVLKVTree[K, V]) Right() *AVLKVTreeNode[K, V] {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Right()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return &AVLKVTreeNode[K, V]{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
}
|
||||
|
||||
// Floor Finds floor node of the input key, returns the floor node or nil if no floor node is found.
|
||||
// The second returned parameter `found` is true if floor was found, otherwise false.
|
||||
//
|
||||
// Floor node is defined as the largest node that is smaller than or equal to the given node.
|
||||
// A floor node may not be found, either because the tree is empty, or because
|
||||
// all nodes in the tree is larger than the given node.
|
||||
//
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLKVTree[K, V]) Floor(key K) (floor *AVLKVTreeNode[K, V], found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node, ok := tree.tree.Floor(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return &AVLKVTreeNode[K, V]{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}, true
|
||||
}
|
||||
|
||||
// Ceiling finds ceiling node of the input key, returns the ceiling node or nil if no ceiling node is found.
|
||||
// The second return parameter `found` is true if ceiling was found, otherwise false.
|
||||
//
|
||||
// Ceiling node is defined as the smallest node that is larger than or equal to the given node.
|
||||
// A ceiling node may not be found, either because the tree is empty, or because
|
||||
// all nodes in the tree is smaller than the given node.
|
||||
//
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLKVTree[K, V]) Ceiling(key K) (ceiling *AVLKVTreeNode[K, V], found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node, ok := tree.tree.Ceiling(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return &AVLKVTreeNode[K, V]{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}, true
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the tree to value-key.
|
||||
// Note that you should guarantee the value is the same type as key,
|
||||
// or else the comparator would panic.
|
||||
//
|
||||
// If the type of value is different with key, you pass the new `comparator`.
|
||||
func (tree *AVLKVTree[K, V]) Flip(comparator ...func(v1, v2 K) int) {
|
||||
var t = new(AVLKVTree[K, V])
|
||||
if len(comparator) > 0 {
|
||||
t = NewAVLKVTree[K, V](comparator[0], tree.mu.IsSafe())
|
||||
} else {
|
||||
t = NewAVLKVTree[K, V](tree.comparator, tree.mu.IsSafe())
|
||||
}
|
||||
var (
|
||||
newKey K
|
||||
newValue V
|
||||
)
|
||||
tree.IteratorAsc(func(key K, value V) bool {
|
||||
if err := gconv.Scan(key, &newValue); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := gconv.Scan(value, &newKey); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.doSet(newKey, newValue)
|
||||
return true
|
||||
})
|
||||
tree.Clear()
|
||||
tree.Sets(t.Map())
|
||||
}
|
||||
|
||||
// doSet inserts key-value pair node into the tree without lock.
|
||||
// If `key` already exists, then its value is updated with the new value.
|
||||
// If `value` is type of <func() any>, it will be executed and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (tree *AVLKVTree[K, V]) doSet(key K, value V) V {
|
||||
if any(value) == nil {
|
||||
return value
|
||||
}
|
||||
tree.tree.Put(key, value)
|
||||
return value
|
||||
}
|
||||
|
||||
// doGet retrieves and returns the value of given key from tree without lock.
|
||||
func (tree *AVLKVTree[K, V]) doGet(key K) (value V, found bool) {
|
||||
return tree.tree.Get(key)
|
||||
}
|
||||
|
||||
// doRemove removes key from tree and returns its associated value without lock.
|
||||
// Note that, the given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *AVLKVTree[K, V]) doRemove(key K) (value V) {
|
||||
value, _ = tree.tree.Get(key)
|
||||
tree.tree.Remove(key)
|
||||
return
|
||||
}
|
||||
474
container/gtree/gtree_k_v_btree.go
Normal file
474
container/gtree/gtree_k_v_btree.go
Normal file
@ -0,0 +1,474 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emirpasic/gods/v2/trees/btree"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// BKVTree holds elements of the B-tree.
|
||||
type BKVTree[K comparable, V any] struct {
|
||||
mu rwmutex.RWMutex
|
||||
comparator func(v1, v2 K) int
|
||||
m int // order (maximum number of children)
|
||||
tree *btree.Tree[K, V]
|
||||
}
|
||||
|
||||
// BKVTreeEntry represents the key-value pair contained within nodes.
|
||||
type BKVTreeEntry[K comparable, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
||||
|
||||
// NewBKVTree instantiates a B-tree with `m` (maximum number of children) and a custom key comparator.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
// Note that the `m` must be greater or equal than 3, or else it panics.
|
||||
func NewBKVTree[K comparable, V any](m int, comparator func(v1, v2 K) int, safe ...bool) *BKVTree[K, V] {
|
||||
return &BKVTree[K, V]{
|
||||
mu: rwmutex.Create(safe...),
|
||||
m: m,
|
||||
comparator: comparator,
|
||||
tree: btree.NewWith[K, V](m, comparator),
|
||||
}
|
||||
}
|
||||
|
||||
// NewBKVTreeFrom instantiates a B-tree with `m` (maximum number of children), a custom key comparator and data map.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewBKVTreeFrom[K comparable, V any](m int, comparator func(v1, v2 K) int, data map[K]V, safe ...bool) *BKVTree[K, V] {
|
||||
tree := NewBKVTree[K, V](m, comparator, safe...)
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// Clone clones and returns a new tree from current tree.
|
||||
func (tree *BKVTree[K, V]) Clone() *BKVTree[K, V] {
|
||||
if tree == nil {
|
||||
return nil
|
||||
}
|
||||
newTree := NewBKVTree[K, V](tree.m, tree.comparator, tree.mu.IsSafe())
|
||||
newTree.Sets(tree.Map())
|
||||
return newTree
|
||||
}
|
||||
|
||||
// Set sets key-value pair into the tree.
|
||||
func (tree *BKVTree[K, V]) Set(key K, value V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the tree.
|
||||
func (tree *BKVTree[K, V]) Sets(data map[K]V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *BKVTree[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *BKVTree[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` within mutex lock.
|
||||
func (tree *BKVTree[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get searches the `key` in the tree and returns its associated `value` or nil if key is not found in tree.
|
||||
//
|
||||
// Note that, the `nil` value from Get function cannot be used to determine key existence, please use Contains function
|
||||
// to do so.
|
||||
func (tree *BKVTree[K, V]) Get(key K) (value V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
value, _ = tree.doGet(key)
|
||||
return
|
||||
}
|
||||
|
||||
// GetOrSet returns its `value` of `key`, or sets value with given `value` if it does not exist and then returns
|
||||
// this value.
|
||||
func (tree *BKVTree[K, V]) GetOrSet(key K, value V) V {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns its `value` of `key`, or sets value with returned value of callback function `f` if it does not
|
||||
// exist and then returns this value.
|
||||
func (tree *BKVTree[K, V]) GetOrSetFunc(key K, f func() V) V {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns its `value` of `key`, or sets value with returned value of callback function `f` if it does
|
||||
// not exist and then returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` within mutex lock.
|
||||
func (tree *BKVTree[K, V]) GetOrSetFuncLock(key K, f func() V) V {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given `key`.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function Get.
|
||||
func (tree *BKVTree[K, V]) GetVar(key K) *gvar.Var {
|
||||
return gvar.New(tree.Get(key))
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function GetOrSet.
|
||||
func (tree *BKVTree[K, V]) GetVarOrSet(key K, value V) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSet(key, value))
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function GetOrSetFunc.
|
||||
func (tree *BKVTree[K, V]) GetVarOrSetFunc(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFunc(key, f))
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function GetOrSetFuncLock.
|
||||
func (tree *BKVTree[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFuncLock(key, f))
|
||||
}
|
||||
|
||||
// Search searches the tree with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (tree *BKVTree[K, V]) Search(key K) (value V, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Get(key)
|
||||
}
|
||||
|
||||
// Contains checks and returns whether given `key` exists in the tree.
|
||||
func (tree *BKVTree[K, V]) Contains(key K) bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
_, ok := tree.doGet(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Size returns number of nodes in the tree.
|
||||
func (tree *BKVTree[K, V]) Size() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size()
|
||||
}
|
||||
|
||||
// IsEmpty returns true if tree does not contain any nodes
|
||||
func (tree *BKVTree[K, V]) IsEmpty() bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size() == 0
|
||||
}
|
||||
|
||||
// Remove removes the node from the tree by `key`, and returns its associated value of `key`.
|
||||
// The given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *BKVTree[K, V]) Remove(key K) (value V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
return tree.doRemove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes key-value pairs from the tree by `keys`.
|
||||
func (tree *BKVTree[K, V]) Removes(keys []K) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
tree.doRemove(key)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all nodes from the tree.
|
||||
func (tree *BKVTree[K, V]) Clear() {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
}
|
||||
|
||||
// Keys returns all keys from the tree in order by its comparator.
|
||||
func (tree *BKVTree[K, V]) Keys() []K {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values from the true in order by its comparator based on the key.
|
||||
func (tree *BKVTree[K, V]) Values() []V {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Values()
|
||||
}
|
||||
|
||||
// Replace clears the data of the tree and sets the nodes by given `data`.
|
||||
func (tree *BKVTree[K, V]) Replace(data map[K]V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Map returns all key-value pairs as map.
|
||||
func (tree *BKVTree[K, V]) Map() map[K]V {
|
||||
m := make(map[K]V, tree.Size())
|
||||
tree.IteratorAsc(func(key K, value V) bool {
|
||||
m[key] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
// MapStrAny returns all key-value items as map[string]any.
|
||||
func (tree *BKVTree[K, V]) MapStrAny() map[string]any {
|
||||
m := make(map[string]any, tree.Size())
|
||||
tree.IteratorAsc(func(key K, value V) bool {
|
||||
m[gconv.String(key)] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
// Print prints the tree to stdout.
|
||||
func (tree *BKVTree[K, V]) Print() {
|
||||
fmt.Println(tree.String())
|
||||
}
|
||||
|
||||
// String returns a string representation of container (for debugging purposes)
|
||||
func (tree *BKVTree[K, V]) String() string {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return gstr.Replace(tree.tree.String(), "BTree\n", "")
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (tree *BKVTree[K, V]) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
|
||||
elements := make(map[string]V)
|
||||
it := tree.tree.Iterator()
|
||||
for it.Next() {
|
||||
elements[gconv.String(it.Key())] = it.Value()
|
||||
}
|
||||
return json.Marshal(&elements)
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
//
|
||||
// Also see IteratorAsc.
|
||||
func (tree *BKVTree[K, V]) Iterator(f func(key K, value V) bool) {
|
||||
tree.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorFrom is alias of IteratorAscFrom.
|
||||
//
|
||||
// Also see IteratorAscFrom.
|
||||
func (tree *BKVTree[K, V]) IteratorFrom(key K, match bool, f func(key K, value V) bool) {
|
||||
tree.IteratorAscFrom(key, match, f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the tree readonly in ascending order with given callback function `f`.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *BKVTree[K, V]) IteratorAsc(f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.Begin(); it.Next(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function `f`.
|
||||
//
|
||||
// The parameter `key` specifies the start entry for iterating.
|
||||
// The parameter `match` specifies whether starting iterating only if the `key` is fully matched, or else using index
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *BKVTree[K, V]) IteratorAscFrom(key K, match bool, f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndexT(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index < len(keys); index++ {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the tree readonly in descending order with given callback function `f`.
|
||||
//
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *BKVTree[K, V]) IteratorDesc(f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.End(); it.Prev(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDescFrom iterates the tree readonly in descending order with given callback function `f`.
|
||||
//
|
||||
// The parameter `key` specifies the start entry for iterating.
|
||||
// The parameter `match` specifies whether starting iterating only if the `key` is fully matched, or else using index
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *BKVTree[K, V]) IteratorDescFrom(key K, match bool, f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndexT(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index >= 0; index-- {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
}
|
||||
|
||||
// Height returns the height of the tree.
|
||||
func (tree *BKVTree[K, V]) Height() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Height()
|
||||
}
|
||||
|
||||
// Left returns the minimum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *BKVTree[K, V]) Left() *BKVTreeEntry[K, V] {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Left()
|
||||
if node == nil || node.Entries == nil || len(node.Entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &BKVTreeEntry[K, V]{
|
||||
Key: node.Entries[0].Key,
|
||||
Value: node.Entries[0].Value,
|
||||
}
|
||||
}
|
||||
|
||||
// Right returns the maximum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *BKVTree[K, V]) Right() *BKVTreeEntry[K, V] {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Right()
|
||||
if node == nil || node.Entries == nil || len(node.Entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &BKVTreeEntry[K, V]{
|
||||
Key: node.Entries[len(node.Entries)-1].Key,
|
||||
Value: node.Entries[len(node.Entries)-1].Value,
|
||||
}
|
||||
}
|
||||
|
||||
// doSet inserts key-value pair node into the tree without lock.
|
||||
// If `key` already exists, then its value is updated with the new value.
|
||||
// If `value` is type of <func() any>, it will be executed and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (tree *BKVTree[K, V]) doSet(key K, value V) V {
|
||||
if any(value) == nil {
|
||||
return value
|
||||
}
|
||||
tree.tree.Put(key, value)
|
||||
return value
|
||||
}
|
||||
|
||||
// doGet get the value from the tree by key without lock.
|
||||
func (tree *BKVTree[K, V]) doGet(key K) (value V, ok bool) {
|
||||
return tree.tree.Get(key)
|
||||
}
|
||||
|
||||
// doRemove removes key from tree and returns its associated value without lock.
|
||||
// Note that, the given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *BKVTree[K, V]) doRemove(key K) (value V) {
|
||||
value, _ = tree.tree.Get(key)
|
||||
tree.tree.Remove(key)
|
||||
return
|
||||
}
|
||||
613
container/gtree/gtree_k_v_redblacktree.go
Normal file
613
container/gtree/gtree_k_v_redblacktree.go
Normal file
@ -0,0 +1,613 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emirpasic/gods/v2/trees/redblacktree"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// RedBlackKVTree holds elements of the red-black tree.
|
||||
type RedBlackKVTree[K comparable, V any] struct {
|
||||
mu rwmutex.RWMutex
|
||||
comparator func(v1, v2 K) int
|
||||
tree *redblacktree.Tree[K, V]
|
||||
}
|
||||
|
||||
// RedBlackKVTreeNode is a single element within the tree.
|
||||
type RedBlackKVTreeNode[K comparable, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
||||
|
||||
// NewRedBlackKVTree instantiates a red-black tree with the custom key comparator.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewRedBlackKVTree[K comparable, V any](comparator func(v1, v2 K) int, safe ...bool) *RedBlackKVTree[K, V] {
|
||||
var tree RedBlackKVTree[K, V]
|
||||
RedBlackKVTreeInit(&tree, comparator, safe...)
|
||||
return &tree
|
||||
}
|
||||
|
||||
// NewRedBlackKVTreeFrom instantiates a red-black tree with the custom key comparator and `data` map.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewRedBlackKVTreeFrom[K comparable, V any](comparator func(v1, v2 K) int, data map[K]V, safe ...bool) *RedBlackKVTree[K, V] {
|
||||
var tree RedBlackKVTree[K, V]
|
||||
RedBlackKVTreeInitFrom(&tree, comparator, data, safe...)
|
||||
return &tree
|
||||
}
|
||||
|
||||
// RedBlackKVTreeInit instantiates a red-black tree with the custom key comparator.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func RedBlackKVTreeInit[K comparable, V any](tree *RedBlackKVTree[K, V], comparator func(v1, v2 K) int, safe ...bool) {
|
||||
if tree == nil {
|
||||
return
|
||||
}
|
||||
tree.mu = rwmutex.Create(safe...)
|
||||
tree.comparator = comparator
|
||||
tree.tree = redblacktree.NewWith[K, V](comparator)
|
||||
}
|
||||
|
||||
// RedBlackKVTreeInitFrom instantiates a red-black tree with the custom key comparator and `data` map.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func RedBlackKVTreeInitFrom[K comparable, V any](tree *RedBlackKVTree[K, V], comparator func(v1, v2 K) int, data map[K]V, safe ...bool) {
|
||||
if tree == nil {
|
||||
return
|
||||
}
|
||||
RedBlackKVTreeInit(tree, comparator, safe...)
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetComparator sets/changes the comparator for sorting.
|
||||
func (tree *RedBlackKVTree[K, V]) SetComparator(comparator func(a, b K) int) {
|
||||
tree.comparator = comparator
|
||||
if tree.tree == nil {
|
||||
tree.tree = redblacktree.NewWith[K, V](comparator)
|
||||
}
|
||||
size := tree.tree.Size()
|
||||
if size > 0 {
|
||||
m := tree.Map()
|
||||
tree.Sets(m)
|
||||
}
|
||||
}
|
||||
|
||||
// Clone clones and returns a new tree from current tree.
|
||||
func (tree *RedBlackKVTree[K, V]) Clone() *RedBlackKVTree[K, V] {
|
||||
if tree == nil {
|
||||
return nil
|
||||
}
|
||||
newTree := NewRedBlackKVTree[K, V](tree.comparator, tree.mu.IsSafe())
|
||||
newTree.Sets(tree.Map())
|
||||
return newTree
|
||||
}
|
||||
|
||||
// Set sets key-value pair into the tree.
|
||||
func (tree *RedBlackKVTree[K, V]) Set(key K, value V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the tree.
|
||||
func (tree *RedBlackKVTree[K, V]) Sets(data map[K]V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for key, value := range data {
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *RedBlackKVTree[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *RedBlackKVTree[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` within mutex lock.
|
||||
func (tree *RedBlackKVTree[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get searches the `key` in the tree and returns its associated `value` or nil if key is not found in tree.
|
||||
//
|
||||
// Note that, the `nil` value from Get function cannot be used to determine key existence, please use Contains function
|
||||
// to do so.
|
||||
func (tree *RedBlackKVTree[K, V]) Get(key K) (value V) {
|
||||
value, _ = tree.Search(key)
|
||||
return
|
||||
}
|
||||
|
||||
// GetOrSet returns its `value` of `key`, or sets value with given `value` if it does not exist and then returns
|
||||
// this value.
|
||||
func (tree *RedBlackKVTree[K, V]) GetOrSet(key K, value V) V {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns its `value` of `key`, or sets value with returned value of callback function `f` if it does not
|
||||
// exist and then returns this value.
|
||||
func (tree *RedBlackKVTree[K, V]) GetOrSetFunc(key K, f func() V) V {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns its `value` of `key`, or sets value with returned value of callback function `f` if it does
|
||||
// not exist and then returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`within mutex lock.
|
||||
func (tree *RedBlackKVTree[K, V]) GetOrSetFuncLock(key K, f func() V) V {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given `key`.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function Get.
|
||||
func (tree *RedBlackKVTree[K, V]) GetVar(key K) *gvar.Var {
|
||||
return gvar.New(tree.Get(key))
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function GetOrSet.
|
||||
func (tree *RedBlackKVTree[K, V]) GetVarOrSet(key K, value V) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSet(key, value))
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function GetOrSetFunc.
|
||||
func (tree *RedBlackKVTree[K, V]) GetVarOrSetFunc(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFunc(key, f))
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
// Note that, the returned gvar.Var is un-concurrent safe.
|
||||
//
|
||||
// Also see function GetOrSetFuncLock.
|
||||
func (tree *RedBlackKVTree[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFuncLock(key, f))
|
||||
}
|
||||
|
||||
// Search searches the tree with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (tree *RedBlackKVTree[K, V]) Search(key K) (value V, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
if node, found := tree.doGet(key); found {
|
||||
return node, true
|
||||
}
|
||||
found = false
|
||||
return
|
||||
}
|
||||
|
||||
// Contains checks and returns whether given `key` exists in the tree.
|
||||
func (tree *RedBlackKVTree[K, V]) Contains(key K) bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
_, ok := tree.doGet(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Size returns number of nodes in the tree.
|
||||
func (tree *RedBlackKVTree[K, V]) Size() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size()
|
||||
}
|
||||
|
||||
// IsEmpty returns true if tree does not contain any nodes.
|
||||
func (tree *RedBlackKVTree[K, V]) IsEmpty() bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size() == 0
|
||||
}
|
||||
|
||||
// Remove removes the node from the tree by `key`, and returns its associated value of `key`.
|
||||
// The given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *RedBlackKVTree[K, V]) Remove(key K) (value V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
return tree.doRemove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes key-value pairs from the tree by `keys`.
|
||||
func (tree *RedBlackKVTree[K, V]) Removes(keys []K) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
tree.doRemove(key)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all nodes from the tree.
|
||||
func (tree *RedBlackKVTree[K, V]) Clear() {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
}
|
||||
|
||||
// Keys returns all keys from the tree in order by its comparator.
|
||||
func (tree *RedBlackKVTree[K, V]) Keys() []K {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values from the true in order by its comparator based on the key.
|
||||
func (tree *RedBlackKVTree[K, V]) Values() []V {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Values()
|
||||
}
|
||||
|
||||
// Replace clears the data of the tree and sets the nodes by given `data`.
|
||||
func (tree *RedBlackKVTree[K, V]) Replace(data map[K]V) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Print prints the tree to stdout.
|
||||
func (tree *RedBlackKVTree[K, V]) Print() {
|
||||
fmt.Println(tree.String())
|
||||
}
|
||||
|
||||
// String returns a string representation of container
|
||||
func (tree *RedBlackKVTree[K, V]) String() string {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return gstr.Replace(tree.tree.String(), "RedBlackTree\n", "")
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (tree RedBlackKVTree[K, V]) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
|
||||
elements := make(map[string]V)
|
||||
it := tree.tree.Iterator()
|
||||
for it.Next() {
|
||||
elements[gconv.String(it.Key())] = it.Value()
|
||||
}
|
||||
return json.Marshal(&elements)
|
||||
}
|
||||
|
||||
// Map returns all key-value pairs as map.
|
||||
func (tree *RedBlackKVTree[K, V]) Map() map[K]V {
|
||||
m := make(map[K]V, tree.Size())
|
||||
tree.IteratorAsc(func(key K, value V) bool {
|
||||
m[key] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
// MapStrAny returns all key-value items as map[string]any.
|
||||
func (tree *RedBlackKVTree[K, V]) MapStrAny() map[string]any {
|
||||
m := make(map[string]any, tree.Size())
|
||||
tree.IteratorAsc(func(key K, value V) bool {
|
||||
m[gconv.String(key)] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
//
|
||||
// Also see IteratorAsc.
|
||||
func (tree *RedBlackKVTree[K, V]) Iterator(f func(key K, value V) bool) {
|
||||
tree.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorFrom is alias of IteratorAscFrom.
|
||||
//
|
||||
// Also see IteratorAscFrom.
|
||||
func (tree *RedBlackKVTree[K, V]) IteratorFrom(key K, match bool, f func(key K, value V) bool) {
|
||||
tree.IteratorAscFrom(key, match, f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the tree readonly in ascending order with given callback function `f`.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *RedBlackKVTree[K, V]) IteratorAsc(f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.Begin(); it.Next(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function `f`.
|
||||
//
|
||||
// The parameter `key` specifies the start entry for iterating.
|
||||
// The parameter `match` specifies whether starting iterating only if the `key` is fully matched, or else using index
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *RedBlackKVTree[K, V]) IteratorAscFrom(key K, match bool, f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndexT(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index < len(keys); index++ {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the tree readonly in descending order with given callback function `f`.
|
||||
//
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *RedBlackKVTree[K, V]) IteratorDesc(f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.End(); it.Prev(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDescFrom iterates the tree readonly in descending order with given callback function `f`.
|
||||
//
|
||||
// The parameter `key` specifies the start entry for iterating.
|
||||
// The parameter `match` specifies whether starting iterating only if the `key` is fully matched, or else using index
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *RedBlackKVTree[K, V]) IteratorDescFrom(key K, match bool, f func(key K, value V) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndexT(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index >= 0; index-- {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
}
|
||||
|
||||
// Left returns the minimum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *RedBlackKVTree[K, V]) Left() *RedBlackKVTreeNode[K, V] {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Left()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return &RedBlackKVTreeNode[K, V]{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
}
|
||||
|
||||
// Right returns the maximum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *RedBlackKVTree[K, V]) Right() *RedBlackKVTreeNode[K, V] {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Right()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return &RedBlackKVTreeNode[K, V]{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
}
|
||||
|
||||
// Floor Finds floor node of the input key, returns the floor node or nil if no floor node is found.
|
||||
// The second returned parameter `found` is true if floor was found, otherwise false.
|
||||
//
|
||||
// Floor node is defined as the largest node that is smaller than or equal to the given node.
|
||||
// A floor node may not be found, either because the tree is empty, or because
|
||||
// all nodes in the tree is larger than the given node.
|
||||
//
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *RedBlackKVTree[K, V]) Floor(key K) (floor *RedBlackKVTreeNode[K, V], found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node, found := tree.tree.Floor(key)
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
return &RedBlackKVTreeNode[K, V]{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}, true
|
||||
}
|
||||
|
||||
// Ceiling finds ceiling node of the input key, returns the ceiling node or nil if no ceiling node is found.
|
||||
// The second return parameter `found` is true if ceiling was found, otherwise false.
|
||||
//
|
||||
// Ceiling node is defined as the smallest node that is larger than or equal to the given node.
|
||||
// A ceiling node may not be found, either because the tree is empty, or because
|
||||
// all nodes in the tree is smaller than the given node.
|
||||
//
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *RedBlackKVTree[K, V]) Ceiling(key K) (ceiling *RedBlackKVTreeNode[K, V], found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node, found := tree.tree.Ceiling(key)
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
return &RedBlackKVTreeNode[K, V]{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}, true
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the tree to value-key.
|
||||
// Note that you should guarantee the value is the same type as key,
|
||||
// or else the comparator would panic.
|
||||
//
|
||||
// If the type of value is different with key, you pass the new `comparator`.
|
||||
func (tree *RedBlackKVTree[K, V]) Flip(comparator ...func(v1, v2 K) int) {
|
||||
var t = new(RedBlackKVTree[K, V])
|
||||
if len(comparator) > 0 {
|
||||
t = NewRedBlackKVTree[K, V](comparator[0], tree.mu.IsSafe())
|
||||
} else {
|
||||
t = NewRedBlackKVTree[K, V](tree.comparator, tree.mu.IsSafe())
|
||||
}
|
||||
var (
|
||||
newKey K
|
||||
newValue V
|
||||
)
|
||||
tree.IteratorAsc(func(key K, value V) bool {
|
||||
if err := gconv.Scan(key, &newValue); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := gconv.Scan(value, &newKey); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.doSet(newKey, newValue)
|
||||
return true
|
||||
})
|
||||
tree.Clear()
|
||||
tree.Sets(t.Map())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (tree *RedBlackKVTree[K, V]) UnmarshalJSON(b []byte) (err error) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if tree.comparator == nil {
|
||||
tree.comparator = gutil.ComparatorTStr[K]
|
||||
tree.tree = redblacktree.NewWith[K, V](tree.comparator)
|
||||
}
|
||||
var data map[string]any
|
||||
if err := json.UnmarshalUseNumber(b, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
var m = make(map[K]V)
|
||||
if err = gconv.Scan(data, &m); err != nil {
|
||||
return
|
||||
}
|
||||
for k, v := range m {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (tree *RedBlackKVTree[K, V]) UnmarshalValue(value any) (err error) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if tree.comparator == nil {
|
||||
tree.comparator = gutil.ComparatorTStr[K]
|
||||
tree.tree = redblacktree.NewWith[K, V](tree.comparator)
|
||||
}
|
||||
var m = make(map[K]V)
|
||||
if err = gconv.Scan(value, &m); err != nil {
|
||||
return
|
||||
}
|
||||
for k, v := range m {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// doSet inserts key-value pair node into the tree without lock.
|
||||
// If `key` already exists, then its value is updated with the new value.
|
||||
// If `value` is type of <func() any>, it will be executed and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (tree *RedBlackKVTree[K, V]) doSet(key K, value V) (ret V) {
|
||||
if any(value) == nil {
|
||||
return
|
||||
}
|
||||
tree.tree.Put(key, value)
|
||||
return value
|
||||
}
|
||||
|
||||
// doGet retrieves and returns the value of given key from tree without lock.
|
||||
func (tree *RedBlackKVTree[K, V]) doGet(key K) (value V, found bool) {
|
||||
return tree.tree.Get(key)
|
||||
}
|
||||
|
||||
// doRemove removes key from tree and returns its associated value without lock.
|
||||
// Note that, the given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *RedBlackKVTree[K, V]) doRemove(key K) (value V) {
|
||||
value, _ = tree.tree.Get(key)
|
||||
tree.tree.Remove(key)
|
||||
return
|
||||
}
|
||||
@ -7,15 +7,9 @@
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emirpasic/gods/trees/redblacktree"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
@ -23,25 +17,19 @@ var _ iTree = (*RedBlackTree)(nil)
|
||||
|
||||
// RedBlackTree holds elements of the red-black tree.
|
||||
type RedBlackTree struct {
|
||||
mu rwmutex.RWMutex
|
||||
comparator func(v1, v2 any) int
|
||||
tree *redblacktree.Tree
|
||||
*RedBlackKVTree[any, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// RedBlackTreeNode is a single element within the tree.
|
||||
type RedBlackTreeNode struct {
|
||||
Key any
|
||||
Value any
|
||||
}
|
||||
type RedBlackTreeNode = RedBlackKVTreeNode[any, any]
|
||||
|
||||
// NewRedBlackTree instantiates a red-black tree with the custom key comparator.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewRedBlackTree(comparator func(v1, v2 any) int, safe ...bool) *RedBlackTree {
|
||||
return &RedBlackTree{
|
||||
mu: rwmutex.Create(safe...),
|
||||
comparator: comparator,
|
||||
tree: redblacktree.NewWith(comparator),
|
||||
RedBlackKVTree: NewRedBlackKVTree[any, any](comparator, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,71 +37,61 @@ func NewRedBlackTree(comparator func(v1, v2 any) int, safe ...bool) *RedBlackTre
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewRedBlackTreeFrom(comparator func(v1, v2 any) int, data map[any]any, safe ...bool) *RedBlackTree {
|
||||
tree := NewRedBlackTree(comparator, safe...)
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
return &RedBlackTree{
|
||||
RedBlackKVTree: NewRedBlackKVTreeFrom(comparator, data, safe...),
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the tree.
|
||||
func (tree *RedBlackTree) lazyInit() {
|
||||
tree.once.Do(func() {
|
||||
if tree.RedBlackKVTree == nil {
|
||||
tree.RedBlackKVTree = NewRedBlackKVTree[any, any](gutil.ComparatorTStr, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// SetComparator sets/changes the comparator for sorting.
|
||||
func (tree *RedBlackTree) SetComparator(comparator func(a, b any) int) {
|
||||
tree.comparator = comparator
|
||||
if tree.tree == nil {
|
||||
tree.tree = redblacktree.NewWith(comparator)
|
||||
}
|
||||
size := tree.tree.Size()
|
||||
if size > 0 {
|
||||
m := tree.Map()
|
||||
tree.Sets(m)
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.SetComparator(comparator)
|
||||
}
|
||||
|
||||
// Clone clones and returns a new tree from current tree.
|
||||
func (tree *RedBlackTree) Clone() *RedBlackTree {
|
||||
newTree := NewRedBlackTree(tree.comparator, tree.mu.IsSafe())
|
||||
newTree.Sets(tree.Map())
|
||||
return newTree
|
||||
if tree == nil {
|
||||
return nil
|
||||
}
|
||||
tree.lazyInit()
|
||||
return &RedBlackTree{
|
||||
RedBlackKVTree: tree.RedBlackKVTree.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets key-value pair into the tree.
|
||||
func (tree *RedBlackTree) Set(key any, value any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.doSet(key, value)
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.Set(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the tree.
|
||||
func (tree *RedBlackTree) Sets(data map[any]any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for key, value := range data {
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.Sets(data)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *RedBlackTree) SetIfNotExist(key any, value any) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and such setting key-value pair operation would be ignored.
|
||||
func (tree *RedBlackTree) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -122,13 +100,8 @@ func (tree *RedBlackTree) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` within mutex lock.
|
||||
func (tree *RedBlackTree) SetIfNotExistFuncLock(key any, f func() any) bool {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if _, ok := tree.doGet(key); !ok {
|
||||
tree.doSet(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Get searches the `key` in the tree and returns its associated `value` or nil if key is not found in tree.
|
||||
@ -136,32 +109,22 @@ func (tree *RedBlackTree) SetIfNotExistFuncLock(key any, f func() any) bool {
|
||||
// Note that, the `nil` value from Get function cannot be used to determine key existence, please use Contains function
|
||||
// to do so.
|
||||
func (tree *RedBlackTree) Get(key any) (value any) {
|
||||
value, _ = tree.Search(key)
|
||||
return
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Get(key)
|
||||
}
|
||||
|
||||
// GetOrSet returns its `value` of `key`, or sets value with given `value` if it does not exist and then returns
|
||||
// this value.
|
||||
func (tree *RedBlackTree) GetOrSet(key any, value any) any {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns its `value` of `key`, or sets value with returned value of callback function `f` if it does not
|
||||
// exist and then returns this value.
|
||||
func (tree *RedBlackTree) GetOrSetFunc(key any, f func() any) any {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns its `value` of `key`, or sets value with returned value of callback function `f` if it does
|
||||
@ -169,13 +132,8 @@ func (tree *RedBlackTree) GetOrSetFunc(key any, f func() any) any {
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`within mutex lock.
|
||||
func (tree *RedBlackTree) GetOrSetFuncLock(key any, f func() any) any {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if v, ok := tree.doGet(key); !ok {
|
||||
return tree.doSet(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a gvar.Var with the value by given `key`.
|
||||
@ -183,7 +141,8 @@ func (tree *RedBlackTree) GetOrSetFuncLock(key any, f func() any) any {
|
||||
//
|
||||
// Also see function Get.
|
||||
func (tree *RedBlackTree) GetVar(key any) *gvar.Var {
|
||||
return gvar.New(tree.Get(key))
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
|
||||
@ -191,7 +150,8 @@ func (tree *RedBlackTree) GetVar(key any) *gvar.Var {
|
||||
//
|
||||
// Also see function GetOrSet.
|
||||
func (tree *RedBlackTree) GetVarOrSet(key any, value any) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSet(key, value))
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
|
||||
@ -199,7 +159,8 @@ func (tree *RedBlackTree) GetVarOrSet(key any, value any) *gvar.Var {
|
||||
//
|
||||
// Also see function GetOrSetFunc.
|
||||
func (tree *RedBlackTree) GetVarOrSetFunc(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFunc(key, f))
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
|
||||
@ -207,158 +168,123 @@ func (tree *RedBlackTree) GetVarOrSetFunc(key any, f func() any) *gvar.Var {
|
||||
//
|
||||
// Also see function GetOrSetFuncLock.
|
||||
func (tree *RedBlackTree) GetVarOrSetFuncLock(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(tree.GetOrSetFuncLock(key, f))
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Search searches the tree with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (tree *RedBlackTree) Search(key any) (value any, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
if node, found := tree.doGet(key); found {
|
||||
return node, true
|
||||
}
|
||||
return nil, false
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Search(key)
|
||||
}
|
||||
|
||||
// Contains checks and returns whether given `key` exists in the tree.
|
||||
func (tree *RedBlackTree) Contains(key any) bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
_, ok := tree.doGet(key)
|
||||
return ok
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns number of nodes in the tree.
|
||||
func (tree *RedBlackTree) Size() int {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size()
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Size()
|
||||
}
|
||||
|
||||
// IsEmpty returns true if tree does not contain any nodes.
|
||||
func (tree *RedBlackTree) IsEmpty() bool {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Size() == 0
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.IsEmpty()
|
||||
}
|
||||
|
||||
// Remove removes the node from the tree by `key`, and returns its associated value of `key`.
|
||||
// The given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *RedBlackTree) Remove(key any) (value any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
return tree.doRemove(key)
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Remove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes key-value pairs from the tree by `keys`.
|
||||
func (tree *RedBlackTree) Removes(keys []any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
tree.doRemove(key)
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.Removes(keys)
|
||||
}
|
||||
|
||||
// Clear removes all nodes from the tree.
|
||||
func (tree *RedBlackTree) Clear() {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.Clear()
|
||||
}
|
||||
|
||||
// Keys returns all keys from the tree in order by its comparator.
|
||||
func (tree *RedBlackTree) Keys() []any {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Keys()
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values from the true in order by its comparator based on the key.
|
||||
func (tree *RedBlackTree) Values() []any {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.Values()
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Values()
|
||||
}
|
||||
|
||||
// Replace clears the data of the tree and sets the nodes by given `data`.
|
||||
func (tree *RedBlackTree) Replace(data map[any]any) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
tree.tree.Clear()
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.Replace(data)
|
||||
}
|
||||
|
||||
// Print prints the tree to stdout.
|
||||
func (tree *RedBlackTree) Print() {
|
||||
fmt.Println(tree.String())
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.Print()
|
||||
}
|
||||
|
||||
// String returns a string representation of container
|
||||
func (tree *RedBlackTree) String() string {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return gstr.Replace(tree.tree.String(), "RedBlackTree\n", "")
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (tree *RedBlackTree) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.tree.MarshalJSON()
|
||||
func (tree RedBlackTree) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.MarshalJSON()
|
||||
}
|
||||
|
||||
// Map returns all key-value pairs as map.
|
||||
func (tree *RedBlackTree) Map() map[any]any {
|
||||
m := make(map[any]any, tree.Size())
|
||||
tree.IteratorAsc(func(key, value any) bool {
|
||||
m[key] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns all key-value items as map[string]any.
|
||||
func (tree *RedBlackTree) MapStrAny() map[string]any {
|
||||
m := make(map[string]any, tree.Size())
|
||||
tree.IteratorAsc(func(key, value any) bool {
|
||||
m[gconv.String(key)] = value
|
||||
return true
|
||||
})
|
||||
return m
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.MapStrAny()
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
//
|
||||
// Also see IteratorAsc.
|
||||
func (tree *RedBlackTree) Iterator(f func(key, value any) bool) {
|
||||
tree.IteratorAsc(f)
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.Iterator(f)
|
||||
}
|
||||
|
||||
// IteratorFrom is alias of IteratorAscFrom.
|
||||
//
|
||||
// Also see IteratorAscFrom.
|
||||
func (tree *RedBlackTree) IteratorFrom(key any, match bool, f func(key, value any) bool) {
|
||||
tree.IteratorAscFrom(key, match, f)
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.IteratorFrom(key, match, f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the tree readonly in ascending order with given callback function `f`.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *RedBlackTree) IteratorAsc(f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.Begin(); it.Next(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function `f`.
|
||||
@ -368,34 +294,16 @@ func (tree *RedBlackTree) IteratorAsc(f func(key, value any) bool) {
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *RedBlackTree) IteratorAscFrom(key any, match bool, f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndex(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index < len(keys); index++ {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.IteratorAscFrom(key, match, f)
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the tree readonly in descending order with given callback function `f`.
|
||||
//
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *RedBlackTree) IteratorDesc(f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var (
|
||||
ok bool
|
||||
it = tree.tree.Iterator()
|
||||
)
|
||||
for it.End(); it.Prev(); {
|
||||
index, value := it.Key(), it.Value()
|
||||
if ok = f(index, value); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.IteratorDesc(f)
|
||||
}
|
||||
|
||||
// IteratorDescFrom iterates the tree readonly in descending order with given callback function `f`.
|
||||
@ -405,44 +313,20 @@ func (tree *RedBlackTree) IteratorDesc(f func(key, value any) bool) {
|
||||
// searching iterating.
|
||||
// If callback function `f` returns true, then it continues iterating; or false to stop.
|
||||
func (tree *RedBlackTree) IteratorDescFrom(key any, match bool, f func(key, value any) bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
var keys = tree.tree.Keys()
|
||||
index, canIterator := iteratorFromGetIndex(key, keys, match)
|
||||
if !canIterator {
|
||||
return
|
||||
}
|
||||
for ; index >= 0; index-- {
|
||||
f(keys[index], tree.Get(keys[index]))
|
||||
}
|
||||
tree.lazyInit()
|
||||
tree.RedBlackKVTree.IteratorDescFrom(key, match, f)
|
||||
}
|
||||
|
||||
// Left returns the minimum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *RedBlackTree) Left() *RedBlackTreeNode {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Left()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return &RedBlackTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Left()
|
||||
}
|
||||
|
||||
// Right returns the maximum element corresponding to the comparator of the tree or nil if the tree is empty.
|
||||
func (tree *RedBlackTree) Right() *RedBlackTreeNode {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node := tree.tree.Right()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return &RedBlackTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Right()
|
||||
}
|
||||
|
||||
// Floor Finds floor node of the input key, returns the floor node or nil if no floor node is found.
|
||||
@ -454,16 +338,8 @@ func (tree *RedBlackTree) Right() *RedBlackTreeNode {
|
||||
//
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *RedBlackTree) Floor(key any) (floor *RedBlackTreeNode, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node, found := tree.tree.Floor(key)
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
return &RedBlackTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}, true
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Floor(key)
|
||||
}
|
||||
|
||||
// Ceiling finds ceiling node of the input key, returns the ceiling node or nil if no ceiling node is found.
|
||||
@ -475,16 +351,8 @@ func (tree *RedBlackTree) Floor(key any) (floor *RedBlackTreeNode, found bool) {
|
||||
//
|
||||
// Key should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *RedBlackTree) Ceiling(key any) (ceiling *RedBlackTreeNode, found bool) {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
node, found := tree.tree.Ceiling(key)
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
return &RedBlackTreeNode{
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
}, true
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.Ceiling(key)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the tree to value-key.
|
||||
@ -493,6 +361,7 @@ func (tree *RedBlackTree) Ceiling(key any) (ceiling *RedBlackTreeNode, found boo
|
||||
//
|
||||
// If the type of value is different with key, you pass the new `comparator`.
|
||||
func (tree *RedBlackTree) Flip(comparator ...func(v1, v2 any) int) {
|
||||
tree.lazyInit()
|
||||
var t = new(RedBlackTree)
|
||||
if len(comparator) > 0 {
|
||||
t = NewRedBlackTree(comparator[0], tree.mu.IsSafe())
|
||||
@ -509,61 +378,12 @@ func (tree *RedBlackTree) Flip(comparator ...func(v1, v2 any) int) {
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (tree *RedBlackTree) UnmarshalJSON(b []byte) error {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if tree.comparator == nil {
|
||||
tree.comparator = gutil.ComparatorString
|
||||
tree.tree = redblacktree.NewWith(tree.comparator)
|
||||
}
|
||||
var data map[string]any
|
||||
if err := json.UnmarshalUseNumber(b, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range data {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
return nil
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (tree *RedBlackTree) UnmarshalValue(value any) (err error) {
|
||||
tree.mu.Lock()
|
||||
defer tree.mu.Unlock()
|
||||
if tree.comparator == nil {
|
||||
tree.comparator = gutil.ComparatorString
|
||||
tree.tree = redblacktree.NewWith(tree.comparator)
|
||||
}
|
||||
for k, v := range gconv.Map(value) {
|
||||
tree.doSet(k, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// doSet inserts key-value pair node into the tree without lock.
|
||||
// If `key` already exists, then its value is updated with the new value.
|
||||
// If `value` is type of <func() any>, it will be executed and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (tree *RedBlackTree) doSet(key, value any) any {
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value == nil {
|
||||
return value
|
||||
}
|
||||
tree.tree.Put(key, value)
|
||||
return value
|
||||
}
|
||||
|
||||
// doGet retrieves and returns the value of given key from tree without lock.
|
||||
func (tree *RedBlackTree) doGet(key any) (value any, found bool) {
|
||||
return tree.tree.Get(key)
|
||||
}
|
||||
|
||||
// doRemove removes key from tree and returns its associated value without lock.
|
||||
// Note that, the given `key` should adhere to the comparator's type assertion, otherwise method panics.
|
||||
func (tree *RedBlackTree) doRemove(key any) (value any) {
|
||||
value, _ = tree.tree.Get(key)
|
||||
tree.tree.Remove(key)
|
||||
return
|
||||
tree.lazyInit()
|
||||
return tree.RedBlackKVTree.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
@ -8,6 +8,11 @@ package gvar
|
||||
|
||||
import "github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
// Bools converts and returns `v` as []bool.
|
||||
func (v *Var) Bools() []bool {
|
||||
return gconv.Bools(v.Val())
|
||||
}
|
||||
|
||||
// Ints converts and returns `v` as []int.
|
||||
func (v *Var) Ints() []int {
|
||||
return gconv.Ints(v.Val())
|
||||
|
||||
@ -21,6 +21,24 @@ func TestVar_Ints(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestVar_Bools(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var arr = []bool{true, false, true, false}
|
||||
objOne := gvar.New(arr, true)
|
||||
t.AssertEQ(objOne.Bools(), arr)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var arr = []int{1, 0, 1, 0}
|
||||
objOne := gvar.New(arr, true)
|
||||
t.AssertEQ(objOne.Bools(), []bool{true, false, true, false})
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var arr = []string{"true", "false", "1", "0"}
|
||||
objOne := gvar.New(arr, true)
|
||||
t.AssertEQ(objOne.Bools(), []bool{true, false, true, false})
|
||||
})
|
||||
}
|
||||
|
||||
func TestVar_Uints(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var arr = []int{1, 2, 3, 4, 5}
|
||||
|
||||
@ -4,13 +4,13 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/apolloconfig/agollo/v4 v4.3.1
|
||||
github.com/gogf/gf/v2 v2.9.5
|
||||
github.com/gogf/gf/v2 v2.9.7
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
|
||||
@ -64,8 +64,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/consul/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.5
|
||||
github.com/gogf/gf/v2 v2.9.7
|
||||
github.com/hashicorp/consul/api v1.24.0
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
)
|
||||
@ -12,7 +12,7 @@ require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
|
||||
@ -24,8 +24,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/kubecm/v2
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.5
|
||||
github.com/gogf/gf/v2 v2.9.7
|
||||
k8s.io/api v0.33.4
|
||||
k8s.io/apimachinery v0.33.4
|
||||
k8s.io/client-go v0.33.4
|
||||
@ -14,7 +14,7 @@ require (
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
|
||||
@ -8,8 +8,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/nacos/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.5
|
||||
github.com/gogf/gf/v2 v2.9.7
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.3.3
|
||||
)
|
||||
|
||||
@ -35,7 +35,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/deckarep/golang-set v1.7.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
|
||||
@ -127,8 +127,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
|
||||
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
||||
@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/polaris/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/v2 v2.9.5
|
||||
github.com/gogf/gf/v2 v2.9.7
|
||||
github.com/polarismesh/polaris-go v1.6.1
|
||||
)
|
||||
|
||||
@ -13,7 +13,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
|
||||
@ -210,8 +210,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
[简体中文](README.zh_CN.MD)
|
||||
|
||||
# Database drivers
|
||||
|
||||
@ -9,15 +8,20 @@ Powerful database drivers for package gdb.
|
||||
Let's take `mysql` for example.
|
||||
|
||||
```shell
|
||||
go get -u github.com/gogf/gf/contrib/drivers/mysql/v2
|
||||
# Easy to copy
|
||||
go get -u github.com/gogf/gf/contrib/drivers/clickhouse/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/dm/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/mssql/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/oracle/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/pgsql/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/sqlite/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/sqlitecgo/v2
|
||||
go get github.com/gogf/gf/contrib/drivers/mysql/v2@latest
|
||||
|
||||
# Easy for copying:
|
||||
go get github.com/gogf/gf/contrib/drivers/clickhouse/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/dm/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/gaussdb/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/mariadb/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/mssql/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/oceanbase/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/oracle/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/pgsql/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/sqlite/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/sqlitecgo/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/tidb/v2@latest
|
||||
```
|
||||
|
||||
Choose and import the driver to your project:
|
||||
@ -44,12 +48,36 @@ func main() {
|
||||
|
||||
## Supported Drivers
|
||||
|
||||
### MySQL/MariaDB/TiDB
|
||||
### MySQL
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
```
|
||||
|
||||
### MariaDB
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/mariadb/v2"
|
||||
```
|
||||
|
||||
### TiDB
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/tidb/v2"
|
||||
```
|
||||
|
||||
### OceanBase
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/oceanbase/v2"
|
||||
```
|
||||
|
||||
### GaussDB
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/gaussdb/v2"
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
```go
|
||||
@ -58,7 +86,7 @@ import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
|
||||
#### cgo version
|
||||
|
||||
When the target is a 32-bit Windows system, the cgo version needs to be used.
|
||||
When the target is a `32-bit` Windows system, the `cgo` version needs to be used.
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/sqlitecgo/v2"
|
||||
@ -70,10 +98,6 @@ import _ "github.com/gogf/gf/contrib/drivers/sqlitecgo/v2"
|
||||
import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- It does not support `Replace` features.
|
||||
|
||||
### SQL Server
|
||||
|
||||
```go
|
||||
@ -82,9 +106,10 @@ import _ "github.com/gogf/gf/contrib/drivers/mssql/v2"
|
||||
|
||||
Note:
|
||||
|
||||
- It does not support `Replace` features.
|
||||
- `InsertIgnore` returns error if there is no primary key or unique index submitted with record.
|
||||
- It supports server version >= `SQL Server2005`
|
||||
- It ONLY supports datetime2 and datetimeoffset types for auto handling created_at/updated_at/deleted_at columns, because datetime type does not support microseconds precision when column value is passed as string.
|
||||
- It ONLY supports `datetime2` and `datetimeoffset` types for auto handling created_at/updated_at/deleted_at columns,
|
||||
because datetime type does not support microseconds precision when column value is passed as string.
|
||||
|
||||
### Oracle
|
||||
|
||||
@ -94,8 +119,8 @@ import _ "github.com/gogf/gf/contrib/drivers/oracle/v2"
|
||||
|
||||
Note:
|
||||
|
||||
- It does not support `Replace` features.
|
||||
- It does not support `LastInsertId`.
|
||||
- `InsertIgnore` returns error if there is no primary key or unique index submitted with record.
|
||||
|
||||
### ClickHouse
|
||||
|
||||
@ -105,7 +130,7 @@ import _ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
|
||||
|
||||
Note:
|
||||
|
||||
- It does not support `InsertIgnore/InsertGetId` features.
|
||||
- It does not support `InsertIgnore/InsertAndGetId` features.
|
||||
- It does not support `Save/Replace` features.
|
||||
- It does not support `Transaction` feature.
|
||||
- It does not support `RowsAffected` feature.
|
||||
@ -118,7 +143,7 @@ import _ "github.com/gogf/gf/contrib/drivers/dm/v2"
|
||||
|
||||
Note:
|
||||
|
||||
- It does not support `Replace` features.
|
||||
- `InsertIgnore` returns error if there is no primary key or unique index submitted with record.
|
||||
|
||||
## Custom Drivers
|
||||
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
# 数据库驱动程序
|
||||
|
||||
用于gdb包的数据库驱动程序。
|
||||
|
||||
## 安装
|
||||
|
||||
以 `mysql` 为例。
|
||||
|
||||
```shell
|
||||
go get -u github.com/gogf/gf/contrib/drivers/mysql/v2
|
||||
# 方便复制
|
||||
go get -u github.com/gogf/gf/contrib/drivers/clickhouse/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/dm/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/mssql/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/oracle/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/pgsql/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/sqlite/v2
|
||||
go get -u github.com/gogf/gf/contrib/drivers/sqlitecgo/v2
|
||||
```
|
||||
|
||||
选择并将驱动程序导入到您的项目中:
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
```
|
||||
|
||||
通常在 `main.go` 的顶部导入:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
|
||||
// 其他导入的包。
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 主要逻辑。
|
||||
}
|
||||
```
|
||||
|
||||
## 支持的驱动程序
|
||||
|
||||
### MySQL/MariaDB/TiDB
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
```
|
||||
|
||||
#### cgo 版本
|
||||
|
||||
32位Windows请使用cgo版本
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/sqlitecgo/v2"
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不支持 `Replace` 功能。
|
||||
|
||||
### SQL Server
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/mssql/v2"
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不支持 `Replace` 功能。
|
||||
- 仅支持服务器版本 >= `SQL Server2005`
|
||||
- 仅支持 datetime2 和 datetimeoffset 类型来自动处理 created_at/updated_at/deleted_at 列,因为 datetime 类型在将列值作为字符串传递时不支持微秒精度。
|
||||
|
||||
### Oracle
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/oracle/v2"
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不支持 `Replace` 功能。
|
||||
- 不支持 `LastInsertId`。
|
||||
|
||||
### ClickHouse
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不支持 `InsertIgnore/InsertGetId` 功能。
|
||||
- 不支持 `Save/Replace` 功能。
|
||||
- 不支持 `Transaction` 功能。
|
||||
- 不支持 `RowsAffected` 功能。
|
||||
|
||||
### DM
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/dm/v2"
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不支持 `Replace` 功能。
|
||||
|
||||
## 自定义驱动程序
|
||||
|
||||
自定义驱动程序非常快速和简单,您可以参考当前驱动程序的源代码来进行开发。
|
||||
如果您有关于支持新驱动程序的PR(Pull Request),我们将非常感激地接受您的提交到当前仓库。
|
||||
@ -16,6 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data for given table.
|
||||
// The list parameter must contain at least one record, which was previously validated.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.15
|
||||
github.com/gogf/gf/v2 v2.9.5
|
||||
github.com/gogf/gf/v2 v2.9.7
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
)
|
||||
@ -12,7 +12,7 @@ require (
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
|
||||
@ -9,8 +9,8 @@ github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// Driver is the driver for dm database.
|
||||
type Driver struct {
|
||||
*gdb.Core
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ func (d *Driver) DoFilter(
|
||||
ctx context.Context, link gdb.Link, sql string, args []any,
|
||||
) (newSql string, newArgs []any, err error) {
|
||||
// There should be no need to capitalize, because it has been done from field processing before
|
||||
newSql, _ = gregex.ReplaceString(`["\n\t]`, "", sql)
|
||||
newSql, _ = gregex.ReplaceString(`["]`, "", sql)
|
||||
newSql = gstr.ReplaceI(gstr.ReplaceI(newSql, "GROUP_CONCAT", "LISTAGG"), "SEPARATOR", ",")
|
||||
|
||||
// TODO The current approach is too rough. We should deal with the GROUP_CONCAT function and the
|
||||
|
||||
@ -20,6 +20,7 @@ import (
|
||||
)
|
||||
|
||||
// DoInsert inserts or updates data for given table.
|
||||
// The list parameter must contain at least one record, which was previously validated.
|
||||
func (d *Driver) DoInsert(
|
||||
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
@ -28,44 +29,92 @@ func (d *Driver) DoInsert(
|
||||
return d.doSave(ctx, link, table, list, option)
|
||||
|
||||
case gdb.InsertOptionReplace:
|
||||
// TODO:: Should be Supported
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported, `Replace operation is not supported by dm driver`,
|
||||
)
|
||||
}
|
||||
// dm does not support REPLACE INTO syntax, use SAVE instead.
|
||||
return d.doSave(ctx, link, table, list, option)
|
||||
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
case gdb.InsertOptionIgnore:
|
||||
// dm does not support INSERT IGNORE syntax, use MERGE instead.
|
||||
return d.doInsertIgnore(ctx, link, table, list, option)
|
||||
|
||||
default:
|
||||
// DM database supports IDENTITY auto-increment columns natively.
|
||||
// The driver automatically returns LastInsertId through sql.Result.
|
||||
//
|
||||
// Note: DM IDENTITY columns cannot accept explicit ID values unless
|
||||
// IDENTITY_INSERT is enabled. When using tables with IDENTITY columns,
|
||||
// avoid providing explicit ID values in the data.
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
}
|
||||
|
||||
// doSave support upsert for dm
|
||||
func (d *Driver) doSave(ctx context.Context,
|
||||
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
if len(option.OnConflict) == 0 {
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeMissingParameter, `Please specify conflict columns`,
|
||||
)
|
||||
}
|
||||
return d.doMergeInsert(ctx, link, table, list, option, true)
|
||||
}
|
||||
|
||||
if len(list) == 0 {
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeInvalidRequest, `Save operation list is empty by oracle driver`,
|
||||
)
|
||||
// doInsertIgnore implements INSERT IGNORE operation using MERGE statement for DM database.
|
||||
// It only inserts records when there's no conflict on primary/unique keys.
|
||||
func (d *Driver) doInsertIgnore(ctx context.Context,
|
||||
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
return d.doMergeInsert(ctx, link, table, list, option, false)
|
||||
}
|
||||
|
||||
// doMergeInsert implements MERGE-based insert operations for DM database.
|
||||
// When withUpdate is true, it performs upsert (insert or update).
|
||||
// When withUpdate is false, it performs insert ignore (insert only when no conflict).
|
||||
func (d *Driver) doMergeInsert(
|
||||
ctx context.Context,
|
||||
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption, withUpdate bool,
|
||||
) (result sql.Result, err error) {
|
||||
// If OnConflict is not specified, automatically get the primary key of the table
|
||||
conflictKeys := option.OnConflict
|
||||
if len(conflictKeys) == 0 {
|
||||
primaryKeys, err := d.Core.GetPrimaryKeys(ctx, table)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(
|
||||
gcode.CodeInternalError,
|
||||
err,
|
||||
`failed to get primary keys for table`,
|
||||
)
|
||||
}
|
||||
foundPrimaryKey := false
|
||||
for _, primaryKey := range primaryKeys {
|
||||
for dataKey := range list[0] {
|
||||
if strings.EqualFold(dataKey, primaryKey) {
|
||||
foundPrimaryKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundPrimaryKey {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundPrimaryKey {
|
||||
return nil, gerror.NewCodef(
|
||||
gcode.CodeMissingParameter,
|
||||
`Replace/Save/InsertIgnore operation requires conflict detection: `+
|
||||
`either specify OnConflict() columns or ensure table '%s' has a primary key in the data`,
|
||||
table,
|
||||
)
|
||||
}
|
||||
// TODO consider composite primary keys.
|
||||
conflictKeys = primaryKeys
|
||||
}
|
||||
|
||||
var (
|
||||
one = list[0]
|
||||
oneLen = len(one)
|
||||
charL, charR = d.GetChars()
|
||||
|
||||
conflictKeys = option.OnConflict
|
||||
one = list[0]
|
||||
oneLen = len(one)
|
||||
charL, charR = d.GetChars()
|
||||
conflictKeySet = gset.New(false)
|
||||
|
||||
// queryHolders: Handle data with Holder that need to be upsert
|
||||
// queryValues: Handle data that need to be upsert
|
||||
// queryHolders: Handle data with Holder that need to be merged
|
||||
// queryValues: Handle data that need to be merged
|
||||
// insertKeys: Handle valid keys that need to be inserted
|
||||
// insertValues: Handle values that need to be inserted
|
||||
// updateValues: Handle values that need to be updated
|
||||
// updateValues: Handle values that need to be updated (only when withUpdate=true)
|
||||
queryHolders = make([]string, oneLen)
|
||||
queryValues = make([]any, oneLen)
|
||||
insertKeys = make([]string, oneLen)
|
||||
@ -86,9 +135,9 @@ func (d *Driver) doSave(ctx context.Context,
|
||||
insertKeys[index] = keyWithChar
|
||||
insertValues[index] = fmt.Sprintf("T2.%s", keyWithChar)
|
||||
|
||||
// filter conflict keys in updateValues.
|
||||
// And the key is not a soft created field.
|
||||
if !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
|
||||
// Build updateValues only when withUpdate is true
|
||||
// Filter conflict keys and soft created fields from updateValues
|
||||
if withUpdate && !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
|
||||
updateValues = append(
|
||||
updateValues,
|
||||
fmt.Sprintf(`T1.%s = T2.%s`, keyWithChar, keyWithChar),
|
||||
@ -97,8 +146,10 @@ func (d *Driver) doSave(ctx context.Context,
|
||||
index++
|
||||
}
|
||||
|
||||
batchResult := new(gdb.SqlResult)
|
||||
sqlStr := parseSqlForUpsert(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
|
||||
var (
|
||||
batchResult = new(gdb.SqlResult)
|
||||
sqlStr = parseSqlForMerge(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
|
||||
)
|
||||
r, err := d.DoExec(ctx, link, sqlStr, queryValues...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
@ -112,40 +163,40 @@ func (d *Driver) doSave(ctx context.Context,
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
// parseSqlForUpsert
|
||||
// MERGE INTO {{table}} T1
|
||||
// USING ( SELECT {{queryHolders}} FROM DUAL T2
|
||||
// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}} AND ...)
|
||||
// WHEN NOT MATCHED THEN
|
||||
// INSERT {{insertKeys}} VALUES {{insertValues}}
|
||||
// WHEN MATCHED THEN
|
||||
// UPDATE SET {{updateValues}}
|
||||
func parseSqlForUpsert(table string,
|
||||
// parseSqlForMerge generates MERGE statement for DM database.
|
||||
// When updateValues is empty, it only inserts (INSERT IGNORE behavior).
|
||||
// When updateValues is provided, it performs upsert (INSERT or UPDATE).
|
||||
// Examples:
|
||||
// - INSERT IGNORE: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...)
|
||||
// - UPSERT: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...) WHEN MATCHED THEN UPDATE SET ...
|
||||
func parseSqlForMerge(table string,
|
||||
queryHolders, insertKeys, insertValues, updateValues, duplicateKey []string,
|
||||
) (sqlStr string) {
|
||||
var (
|
||||
queryHolderStr = strings.Join(queryHolders, ",")
|
||||
insertKeyStr = strings.Join(insertKeys, ",")
|
||||
insertValueStr = strings.Join(insertValues, ",")
|
||||
updateValueStr = strings.Join(updateValues, ",")
|
||||
duplicateKeyStr string
|
||||
pattern = gstr.Trim(`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s) WHEN MATCHED THEN UPDATE SET %s;`)
|
||||
)
|
||||
|
||||
// Build ON condition
|
||||
for index, keys := range duplicateKey {
|
||||
if index != 0 {
|
||||
duplicateKeyStr += " AND "
|
||||
}
|
||||
duplicateTmp := fmt.Sprintf("T1.%s = T2.%s", keys, keys)
|
||||
duplicateKeyStr += duplicateTmp
|
||||
duplicateKeyStr += fmt.Sprintf("T1.%s = T2.%s", keys, keys)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(pattern,
|
||||
table,
|
||||
queryHolderStr,
|
||||
duplicateKeyStr,
|
||||
insertKeyStr,
|
||||
insertValueStr,
|
||||
updateValueStr,
|
||||
)
|
||||
// Build SQL based on whether UPDATE is needed
|
||||
pattern := gstr.Trim(`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s)`)
|
||||
if len(updateValues) > 0 {
|
||||
// Upsert: INSERT or UPDATE
|
||||
pattern += gstr.Trim(`WHEN MATCHED THEN UPDATE SET %s`)
|
||||
return fmt.Sprintf(
|
||||
pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr,
|
||||
strings.Join(updateValues, ","),
|
||||
)
|
||||
}
|
||||
// Insert Ignore: INSERT only
|
||||
return fmt.Sprintf(pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr)
|
||||
}
|
||||
|
||||
@ -11,12 +11,21 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// escapeSingleQuote escapes single quotes in the string to prevent SQL injection.
|
||||
// In SQL, single quotes are escaped by doubling them (two single quotes).
|
||||
func escapeSingleQuote(s string) string {
|
||||
return strings.ReplaceAll(s, "'", "''")
|
||||
}
|
||||
|
||||
const (
|
||||
tableFieldsSqlTmp = `SELECT * FROM ALL_TAB_COLUMNS WHERE Table_Name= '%s' AND OWNER = '%s'`
|
||||
tableFieldsSqlTmp = `SELECT c.COLUMN_NAME, c.DATA_TYPE, c.DATA_LENGTH, c.DATA_DEFAULT, c.NULLABLE, cc.COMMENTS FROM ALL_TAB_COLUMNS c LEFT JOIN ALL_COL_COMMENTS cc ON c.COLUMN_NAME = cc.COLUMN_NAME AND c.TABLE_NAME = cc.TABLE_NAME AND c.OWNER = cc.OWNER WHERE c.TABLE_NAME = '%s' AND c.OWNER = '%s'`
|
||||
tableFieldsPkSqlSchemaTmp = `SELECT COLS.COLUMN_NAME AS PRIMARY_KEY_COLUMN FROM USER_CONSTRAINTS CONS JOIN USER_CONS_COLUMNS COLS ON CONS.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME WHERE CONS.TABLE_NAME = '%s' AND CONS.CONSTRAINT_TYPE = 'P'`
|
||||
tableFieldsPkSqlDBATmp = `SELECT COLS.COLUMN_NAME AS PRIMARY_KEY_COLUMN FROM DBA_CONSTRAINTS CONS JOIN DBA_CONS_COLUMNS COLS ON CONS.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME WHERE CONS.TABLE_NAME = '%s' AND CONS.OWNER = '%s' AND CONS.CONSTRAINT_TYPE = 'P'`
|
||||
)
|
||||
|
||||
// TableFields retrieves and returns the fields' information of specified table of current schema.
|
||||
@ -24,8 +33,9 @@ func (d *Driver) TableFields(
|
||||
ctx context.Context, table string, schema ...string,
|
||||
) (fields map[string]*gdb.TableField, err error) {
|
||||
var (
|
||||
result gdb.Result
|
||||
link gdb.Link
|
||||
result gdb.Result
|
||||
pkResult gdb.Result
|
||||
link gdb.Link
|
||||
// When no schema is specified, the configuration item is returned by default
|
||||
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||
)
|
||||
@ -38,14 +48,35 @@ func (d *Driver) TableFields(
|
||||
ctx, link,
|
||||
fmt.Sprintf(
|
||||
tableFieldsSqlTmp,
|
||||
strings.ToUpper(table),
|
||||
strings.ToUpper(d.GetSchema()),
|
||||
escapeSingleQuote(strings.ToUpper(table)),
|
||||
escapeSingleQuote(strings.ToUpper(d.GetSchema())),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Query the primary key field
|
||||
pkResult, err = d.DoSelect(
|
||||
ctx, link,
|
||||
fmt.Sprintf(tableFieldsPkSqlSchemaTmp, escapeSingleQuote(strings.ToUpper(table))),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pkResult.IsEmpty() {
|
||||
pkResult, err = d.DoSelect(
|
||||
ctx, link,
|
||||
fmt.Sprintf(tableFieldsPkSqlDBATmp, escapeSingleQuote(strings.ToUpper(table)), escapeSingleQuote(strings.ToUpper(d.GetSchema()))),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
pkFields := gmap.NewStrStrMap()
|
||||
for _, pk := range pkResult {
|
||||
pkFields.Set(pk["PRIMARY_KEY_COLUMN"].String(), "PRI")
|
||||
}
|
||||
for i, m := range result {
|
||||
// m[NULLABLE] returns "N" "Y"
|
||||
// "N" means not null
|
||||
@ -54,15 +85,29 @@ func (d *Driver) TableFields(
|
||||
if m["NULLABLE"].String() != "N" {
|
||||
nullable = true
|
||||
}
|
||||
|
||||
// Build field type with length/precision
|
||||
// For NUMBER(p,s): use DATA_PRECISION and DATA_SCALE
|
||||
// For VARCHAR2/CHAR: use DATA_LENGTH
|
||||
var (
|
||||
fieldType string
|
||||
dataType = m["DATA_TYPE"].String()
|
||||
dataLength = m["DATA_LENGTH"].Int()
|
||||
)
|
||||
if dataLength > 0 {
|
||||
fieldType = fmt.Sprintf("%s(%d)", dataType, dataLength)
|
||||
} else {
|
||||
fieldType = dataType
|
||||
}
|
||||
fields[m["COLUMN_NAME"].String()] = &gdb.TableField{
|
||||
Index: i,
|
||||
Name: m["COLUMN_NAME"].String(),
|
||||
Type: m["DATA_TYPE"].String(),
|
||||
Type: fieldType,
|
||||
Null: nullable,
|
||||
Default: m["DATA_DEFAULT"].Val(),
|
||||
// Key: m["Key"].String(),
|
||||
Key: pkFields.Get(m["COLUMN_NAME"].String()),
|
||||
// Extra: m["Extra"].String(),
|
||||
// Comment: m["Comment"].String(),
|
||||
Comment: m["COMMENTS"].String(),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
package dm_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -28,10 +27,7 @@ func Test_DB_Ping(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTables(t *testing.T) {
|
||||
tables := []string{"A_tables", "A_tables2"}
|
||||
for _, v := range tables {
|
||||
createInitTable(v)
|
||||
}
|
||||
tables := createInitTables(2)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Tables(ctx)
|
||||
gtest.AssertNil(err)
|
||||
@ -39,7 +35,7 @@ func TestTables(t *testing.T) {
|
||||
for i := 0; i < len(tables); i++ {
|
||||
find := false
|
||||
for j := 0; j < len(result); j++ {
|
||||
if strings.ToUpper(tables[i]) == result[j] {
|
||||
if strings.ToUpper(tables[i]) == strings.ToUpper(result[j]) {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
@ -52,7 +48,7 @@ func TestTables(t *testing.T) {
|
||||
for i := 0; i < len(tables); i++ {
|
||||
find := false
|
||||
for j := 0; j < len(result); j++ {
|
||||
if strings.ToUpper(tables[i]) == result[j] {
|
||||
if strings.ToUpper(tables[i]) == strings.ToUpper(result[j]) {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
@ -83,17 +79,14 @@ func TestTableFields(t *testing.T) {
|
||||
createInitTable(tables)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var expect = map[string][]any{
|
||||
"ID": {"BIGINT", false},
|
||||
"ACCOUNT_NAME": {"VARCHAR", false},
|
||||
"PWD_RESET": {"TINYINT", false},
|
||||
"ATTR_INDEX": {"INT", true},
|
||||
"DELETED": {"INT", false},
|
||||
"CREATED_TIME": {"TIMESTAMP", false},
|
||||
"ID": {"BIGINT(8)", false},
|
||||
"ACCOUNT_NAME": {"VARCHAR(128)", false},
|
||||
"PWD_RESET": {"TINYINT(1)", false},
|
||||
"ATTR_INDEX": {"INT(4)", true},
|
||||
"DELETED": {"INT(4)", false},
|
||||
"CREATED_TIME": {"TIMESTAMP(8)", false},
|
||||
}
|
||||
|
||||
_, err := dbErr.TableFields(ctx, "Fields")
|
||||
gtest.AssertNE(err, nil)
|
||||
|
||||
res, err := db.TableFields(ctx, tables)
|
||||
gtest.AssertNil(err)
|
||||
|
||||
@ -114,6 +107,14 @@ func TestTableFields(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestTableFields_WithWrongPassword(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// dbErr is configured with wrong password, so it should return an error
|
||||
_, err := dbErr.TableFields(ctx, "Fields")
|
||||
gtest.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Query(t *testing.T) {
|
||||
tableName := "A_tables"
|
||||
createInitTable(tableName)
|
||||
@ -138,110 +139,6 @@ func Test_DB_Query(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestModelSave(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
AccountName string
|
||||
AttrIndex int
|
||||
}
|
||||
var (
|
||||
user User
|
||||
count int
|
||||
result sql.Result
|
||||
err error
|
||||
)
|
||||
db.SetDebug(true)
|
||||
|
||||
result, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"accountName": "ac1",
|
||||
"attrIndex": 100,
|
||||
}).OnConflict("id").Save()
|
||||
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 1)
|
||||
t.Assert(user.AccountName, "ac1")
|
||||
t.Assert(user.AttrIndex, 100)
|
||||
|
||||
_, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"accountName": "ac2",
|
||||
"attrIndex": 200,
|
||||
}).OnConflict("id").Save()
|
||||
t.AssertNil(err)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.AccountName, "ac2")
|
||||
t.Assert(user.AttrIndex, 200)
|
||||
|
||||
count, err = db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestModelInsert(t *testing.T) {
|
||||
// g.Model.insert not lost default not null coloumn
|
||||
table := "A_tables"
|
||||
createInitTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i := 200
|
||||
data := User{
|
||||
ID: int64(i),
|
||||
AccountName: fmt.Sprintf(`A%dtwo`, i),
|
||||
PwdReset: 0,
|
||||
AttrIndex: 99,
|
||||
CreatedTime: time.Now(),
|
||||
UpdatedTime: time.Now(),
|
||||
}
|
||||
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
|
||||
_, err := db.Model(table).Insert(&data)
|
||||
gtest.AssertNil(err)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i := 201
|
||||
data := User{
|
||||
ID: int64(i),
|
||||
AccountName: fmt.Sprintf(`A%dtwoONE`, i),
|
||||
PwdReset: 1,
|
||||
CreatedTime: time.Now(),
|
||||
AttrIndex: 98,
|
||||
UpdatedTime: time.Now(),
|
||||
}
|
||||
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
|
||||
_, err := db.Model(table).Data(&data).Insert()
|
||||
gtest.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDBInsert(t *testing.T) {
|
||||
table := "A_tables"
|
||||
createInitTable("A_tables")
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i := 300
|
||||
data := g.Map{
|
||||
"ID": i,
|
||||
"ACCOUNT_NAME": fmt.Sprintf(`A%dthress`, i),
|
||||
"PWD_RESET": 3,
|
||||
"ATTR_INDEX": 98,
|
||||
"CREATED_TIME": gtime.Now(),
|
||||
"UPDATED_TIME": gtime.Now(),
|
||||
}
|
||||
_, err := db.Insert(ctx, table, &data)
|
||||
gtest.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Exec(t *testing.T) {
|
||||
createInitTable("A_tables")
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
1400
contrib/drivers/dm/dm_z_unit_feature_soft_time_test.go
Normal file
1400
contrib/drivers/dm/dm_z_unit_feature_soft_time_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -63,11 +63,10 @@ func init() {
|
||||
Weight: 1,
|
||||
MaxIdleConnCount: 10,
|
||||
MaxOpenConnCount: 10,
|
||||
CreatedAt: "created_time",
|
||||
UpdatedAt: "updated_time",
|
||||
// CreatedAt: "created_time",
|
||||
// UpdatedAt: "updated_time",
|
||||
}
|
||||
|
||||
// todo
|
||||
nodeLink := gdb.ConfigNode{
|
||||
Type: TestDBType,
|
||||
Name: TestDBName,
|
||||
@ -111,6 +110,8 @@ func init() {
|
||||
}
|
||||
|
||||
ctx = context.Background()
|
||||
|
||||
// db.SetDebug(true)
|
||||
}
|
||||
|
||||
func dropTable(table string) {
|
||||
@ -143,7 +144,7 @@ func createTable(table ...string) (name string) {
|
||||
CREATE TABLE "%s"
|
||||
(
|
||||
"ID" BIGINT NOT NULL,
|
||||
"ACCOUNT_NAME" VARCHAR(128) DEFAULT '' NOT NULL,
|
||||
"ACCOUNT_NAME" VARCHAR(128) DEFAULT '' NOT NULL COMMENT 'Account Name',
|
||||
"PWD_RESET" TINYINT DEFAULT 0 NOT NULL,
|
||||
"ENABLED" INT DEFAULT 1 NOT NULL,
|
||||
"DELETED" INT DEFAULT 0 NOT NULL,
|
||||
@ -156,7 +157,6 @@ NOT CLUSTER PRIMARY KEY("ID")) STORAGE(ON "MAIN", CLUSTERBTR) ;
|
||||
`, name)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ func createInitTable(table ...string) (name string) {
|
||||
"account_name": fmt.Sprintf(`name_%d`, i),
|
||||
"pwd_reset": 0,
|
||||
"attr_index": i,
|
||||
"create_time": gtime.Now().String(),
|
||||
"created_time": gtime.Now(),
|
||||
})
|
||||
}
|
||||
result, err := db.Schema(TestDBName).Insert(context.Background(), name, array.Slice())
|
||||
@ -212,3 +212,41 @@ NOT CLUSTER PRIMARY KEY("ID")) STORAGE(ON "MAIN", CLUSTERBTR) ;
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func createInitTables(len int) []string {
|
||||
tables := make([]string, 0, len)
|
||||
for range len {
|
||||
tables = append(tables, createInitTable())
|
||||
}
|
||||
return tables
|
||||
}
|
||||
|
||||
// createTableWithIdentity creates a table with IDENTITY column for LastInsertId testing
|
||||
func createTableWithIdentity(table ...string) (name string) {
|
||||
if len(table) > 0 {
|
||||
name = table[0]
|
||||
} else {
|
||||
name = fmt.Sprintf("random_%d", gtime.Timestamp())
|
||||
}
|
||||
|
||||
dropTable(name)
|
||||
|
||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||
CREATE TABLE "%s"
|
||||
(
|
||||
"ID" BIGINT IDENTITY(1, 1) NOT NULL,
|
||||
"ACCOUNT_NAME" VARCHAR(128) DEFAULT '' NOT NULL COMMENT 'Account Name',
|
||||
"PWD_RESET" TINYINT DEFAULT 0 NOT NULL,
|
||||
"ENABLED" INT DEFAULT 1 NOT NULL,
|
||||
"DELETED" INT DEFAULT 0 NOT NULL,
|
||||
"ATTR_INDEX" INT DEFAULT 0 ,
|
||||
"CREATED_BY" VARCHAR(32) DEFAULT '' NOT NULL,
|
||||
"CREATED_TIME" TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP() NOT NULL,
|
||||
"UPDATED_BY" VARCHAR(32) DEFAULT '' NOT NULL,
|
||||
"UPDATED_TIME" TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP() NOT NULL,
|
||||
NOT CLUSTER PRIMARY KEY("ID")) STORAGE(ON "MAIN", CLUSTERBTR) ;
|
||||
`, name)); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -71,3 +71,93 @@ func Test_Issue2594(t *testing.T) {
|
||||
t.Assert(h1, h2)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_MultilineSQLStatement tests that multi-line SQL statements are properly supported.
|
||||
// This test verifies that newlines and tabs in SQL queries are preserved,
|
||||
// which is essential for readability and proper SQL statement handling.
|
||||
func Test_MultilineSQLStatement(t *testing.T) {
|
||||
table := "A_tables"
|
||||
createInitTable(table)
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test multi-line SELECT statement with newlines and indentation
|
||||
multilineSql := `
|
||||
SELECT
|
||||
id,
|
||||
account_name,
|
||||
attr_index
|
||||
FROM A_tables
|
||||
WHERE id = ?
|
||||
AND account_name = ?
|
||||
`
|
||||
result, err := db.GetAll(ctx, multilineSql, 1, "name_1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 1)
|
||||
t.Assert(result[0]["ID"].Int(), 1)
|
||||
t.Assert(result[0]["ACCOUNT_NAME"].String(), "name_1")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test multi-line SELECT with tabs
|
||||
multilineSql := `SELECT
|
||||
id,
|
||||
account_name,
|
||||
attr_index
|
||||
FROM A_tables
|
||||
WHERE id IN (?, ?)
|
||||
ORDER BY id`
|
||||
result, err := db.GetAll(ctx, multilineSql, 2, 3)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 2)
|
||||
t.Assert(result[0]["ID"].Int(), 2)
|
||||
t.Assert(result[1]["ID"].Int(), 3)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test that newlines in values don't cause issues
|
||||
multilineSql := `
|
||||
SELECT *
|
||||
FROM A_tables
|
||||
WHERE id = ?`
|
||||
result, err := db.GetAll(ctx, multilineSql, 5)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 1)
|
||||
t.Assert(result[0]["ID"].Int(), 5)
|
||||
t.Assert(result[0]["ACCOUNT_NAME"].String(), "name_5")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test multi-line INSERT with newlines
|
||||
multilineSql := `
|
||||
INSERT INTO A_tables
|
||||
(ID, ACCOUNT_NAME, ATTR_INDEX, CREATED_TIME, UPDATED_TIME)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?)`
|
||||
_, err := db.Exec(ctx, multilineSql, 1001, "multiline_insert_test", 100, gtime.Now(), gtime.Now())
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify the insert worked
|
||||
result, err := db.GetAll(ctx, "SELECT * FROM A_tables WHERE ID = ?", 1001)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 1)
|
||||
t.Assert(result[0]["ACCOUNT_NAME"].String(), "multiline_insert_test")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test multi-line UPDATE with newlines
|
||||
multilineSql := `
|
||||
UPDATE A_tables
|
||||
SET account_name = ?,
|
||||
attr_index = ?
|
||||
WHERE id = ?`
|
||||
_, err := db.Exec(ctx, multilineSql, "updated_multiline", 999, 1)
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify the update worked
|
||||
result, err := db.GetAll(ctx, "SELECT * FROM A_tables WHERE ID = ?", 1)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 1)
|
||||
t.Assert(result[0]["ACCOUNT_NAME"].String(), "updated_multiline")
|
||||
})
|
||||
}
|
||||
|
||||
185
contrib/drivers/dm/dm_z_unit_model_test.go
Normal file
185
contrib/drivers/dm/dm_z_unit_model_test.go
Normal file
@ -0,0 +1,185 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package dm_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Model_Save(t *testing.T) {
|
||||
table := createTableWithIdentity()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
AccountName string
|
||||
AttrIndex int
|
||||
}
|
||||
var (
|
||||
user User
|
||||
count int
|
||||
result sql.Result
|
||||
err error
|
||||
)
|
||||
|
||||
// First insert: let IDENTITY auto-generate ID - use Insert() instead of Save()
|
||||
// because Save() requires a primary key in the data for conflict detection
|
||||
result, err = db.Model(table).Data(g.Map{
|
||||
"accountName": "ac1",
|
||||
"attrIndex": 100,
|
||||
}).Insert()
|
||||
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.AssertGT(user.Id, 0) // ID should be auto-generated
|
||||
t.Assert(user.AccountName, "ac1")
|
||||
t.Assert(user.AttrIndex, 100)
|
||||
|
||||
// Second save: update the existing record using the generated ID
|
||||
_, err = db.Model(table).Data(g.Map{
|
||||
"id": user.Id,
|
||||
"accountName": "ac2",
|
||||
"attrIndex": 200,
|
||||
}).OnConflict("id").Save()
|
||||
t.AssertNil(err)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.AccountName, "ac2")
|
||||
t.Assert(user.AttrIndex, 200)
|
||||
|
||||
_, err = db.Model(table).Data(g.Map{
|
||||
"id": user.Id,
|
||||
"accountName": "ac2",
|
||||
"attrIndex": 2000,
|
||||
}).Save()
|
||||
t.AssertNil(err)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.AccountName, "ac2")
|
||||
t.Assert(user.AttrIndex, 2000)
|
||||
|
||||
count, err = db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert(t *testing.T) {
|
||||
// g.Model.insert not lost default not null column
|
||||
table := "A_tables"
|
||||
createInitTable(table)
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i := 200
|
||||
data := User{
|
||||
ID: int64(i),
|
||||
AccountName: fmt.Sprintf(`A%dtwo`, i),
|
||||
PwdReset: 0,
|
||||
AttrIndex: 99,
|
||||
CreatedTime: time.Now(),
|
||||
UpdatedTime: time.Now(),
|
||||
}
|
||||
result, err := db.Model(table).Insert(&data)
|
||||
gtest.AssertNil(err)
|
||||
n, err := result.RowsAffected()
|
||||
gtest.AssertNil(err)
|
||||
gtest.Assert(n, 1)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i := 201
|
||||
data := User{
|
||||
ID: int64(i),
|
||||
AccountName: fmt.Sprintf(`A%dtwoONE`, i),
|
||||
PwdReset: 1,
|
||||
CreatedTime: time.Now(),
|
||||
AttrIndex: 98,
|
||||
UpdatedTime: time.Now(),
|
||||
}
|
||||
result, err := db.Model(table).Data(&data).Insert()
|
||||
gtest.AssertNil(err)
|
||||
n, err := result.RowsAffected()
|
||||
gtest.AssertNil(err)
|
||||
gtest.Assert(n, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_InsertIgnore(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// db.SetDebug(true)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"account_name": fmt.Sprintf(`name_%d`, 777),
|
||||
"pwd_reset": 0,
|
||||
"attr_index": 777,
|
||||
"created_time": gtime.Now(),
|
||||
}
|
||||
_, err := db.Model(table).Data(data).InsertIgnore()
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err := db.Model(table).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["ACCOUNT_NAME"].String(), "name_1")
|
||||
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
// "id": 1,
|
||||
"account_name": fmt.Sprintf(`name_%d`, 777),
|
||||
"pwd_reset": 0,
|
||||
"attr_index": 777,
|
||||
"created_time": gtime.Now(),
|
||||
}
|
||||
_, err := db.Model(table).Data(data).InsertIgnore()
|
||||
t.AssertNE(err, nil)
|
||||
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_InsertAndGetId(t *testing.T) {
|
||||
table := createTableWithIdentity()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
// "id": 1,
|
||||
"account_name": fmt.Sprintf(`name_%d`, 1),
|
||||
"pwd_reset": 0,
|
||||
"attr_index": 1,
|
||||
"created_time": gtime.Now(),
|
||||
}
|
||||
lastId, err := db.Model(table).Data(data).InsertAndGetId()
|
||||
t.AssertNil(err)
|
||||
t.AssertGT(lastId, 0)
|
||||
})
|
||||
|
||||
}
|
||||
40
contrib/drivers/dm/dm_z_unit_pr_test.go
Normal file
40
contrib/drivers/dm/dm_z_unit_pr_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package dm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
// PR #4157 WherePri
|
||||
func Test_WherePri_PR4157(t *testing.T) {
|
||||
tableName := "A_tables"
|
||||
createInitTable(tableName)
|
||||
defer dropTable(tableName)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var resOne *User
|
||||
err := db.Model(tableName).WherePri(1).Scan(&resOne)
|
||||
t.AssertNil(err)
|
||||
t.AssertNQ(resOne, nil)
|
||||
t.AssertEQ(resOne.ID, int64(1))
|
||||
})
|
||||
}
|
||||
|
||||
// PR #4157 get table field comments
|
||||
func Test_TableFields_Comment_PR4157(t *testing.T) {
|
||||
tableName := "A_tables"
|
||||
schema := "SYSDBA"
|
||||
createInitTable(tableName)
|
||||
defer dropTable(tableName)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
fields, err := db.Model().TableFields(tableName, schema)
|
||||
t.AssertNil(err)
|
||||
t.AssertEQ(fields["ACCOUNT_NAME"].Comment, "Account Name")
|
||||
})
|
||||
}
|
||||
@ -6,13 +6,13 @@ replace github.com/gogf/gf/v2 => ../../../
|
||||
|
||||
require (
|
||||
gitee.com/chunanyong/dm v1.8.12
|
||||
github.com/gogf/gf/v2 v2.9.5
|
||||
github.com/gogf/gf/v2 v2.9.7
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user