Compare commits

...

16 Commits

Author SHA1 Message Date
bb9133ab9d fix: v2.9.6 (#4537) 2025-12-04 11:35:32 +08:00
48845c3473 fix(contrib/drivers/mssql): update tables SQL query for better compatibility (#4170)
修复gf gen在sqlserver上的异常问题:

1. https://github.com/gogf/gf/issues/1722
2. https://github.com/gogf/gf/issues/1761

```powershell
> gf gen dao
fetching tables failed: SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME: mssql: 对象名 
'SYSOBJECTS' 无效。
1. SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME
2. mssql: 对象名 'SYSOBJECTS' 无效。
```

在SqlServer 2022已测试通过:


![image](https://github.com/user-attachments/assets/9f6b7326-c790-4458-93dd-04782b617692)

---------

Co-authored-by: hailaz <739476267@qq.com>
2025-12-03 23:42:16 +08:00
ea956189bf feat(contrib/drivers/dm): add WherePri support (#4157)
The Dameng database supports the wherepri method.
eg: `dao.User.Ctx(ctx).WherePri(id)`

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: John Guo <claymore1986@gmail.com>
Co-authored-by: hailaz <739476267@qq.com>
2025-12-03 17:52:05 +08:00
3912d97811 fix(contrib/drivers/dm): support muti-line sql statement (#4163) (#4164)
Co-authored-by: hailaz <739476267@qq.com>
2025-12-03 16:18:47 +08:00
50fb349bc9 docs: update Chinese documentation and add README.zh_CN.MD (#4534)
Enhance the Chinese documentation by adding a new README file and
updating existing database driver instructions with the latest `go get`
commands. Additionally, provide Chinese explanations for the `gf`
command documentation.

fix https://github.com/gogf/gf/issues/4533
2025-12-01 09:35:06 +08:00
777d7aabb5 feat(container/gtree): add generic tree feature (#4522)
add generic tree feature
improve gmap.TreeMap

---------

Co-authored-by: hailaz <739476267@qq.com>
2025-11-29 21:09:43 +08:00
5a67aac85d feat(container/gmap): add generic list map feature (#4520)
add the generic list map: ListKVMap[K,V] and let ListMap base on it.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: hailaz <739476267@qq.com>
2025-11-29 20:57:41 +08:00
132a5ab9a3 feat(container/gmap): add generic map feature (#4484)
add hash kvmap and let other hash map base on it.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: hailaz <739476267@qq.com>
2025-11-28 21:41:30 +08:00
8575f01273 feat(container/gqueue): add generic queuefeature (#4497)
add TQueue

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: hailaz <739476267@qq.com>
2025-11-28 12:42:12 +08:00
ac75026716 feat(container/gring): add generic ring feature (#4496)
add TRing

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: hailaz <739476267@qq.com>
2025-11-28 11:50:09 +08:00
485a9637cc feat(container/gpool): add generic pool feature (#4493)
add TPool[T] and let Pool base on it.

---------

Co-authored-by: hailaz <739476267@qq.com>
2025-11-27 18:18:37 +08:00
b57b49ecca fix(ci): Free Disk Space (#4529)
改用新的方法,清理其他不必要的目录以获取更多可用空间
2025-11-27 16:47:10 +08:00
cdead46c79 fix(ci): update script permissions and add docker cleanup functionality (#4523) 2025-11-25 14:55:56 +08:00
a4883e6e3d feat(container/gset): add generic set feature (#4492)
Add generic set featrue: TSet[T]

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: hailaz <739476267@qq.com>
2025-11-24 17:47:36 +08:00
fe8ba5e35f fix(database/gdb): Resolve column ambiguity in GROUP BY/ORDER BY with MySQL JOIN (#4521)
When using JOIN queries in MySQL with the `Group()` method, column names
in GROUP BY clauses become ambiguous if multiple tables contain columns
with the same name (commonly `id`). This results in MySQL errors like
"Column 'id' in group statement is ambiguous".

**Example Issue:**
```go
model := t.Ctx(ctx).Fields("t_inf_job.*, t_inf_job_attr.*").
    LeftJoin("t_inf_job_attr", "t_inf_job.id = t_inf_job_attr.job_id").
    Where(t.Columns().Deleted, 0)

// This would fail with "Column 'id' in group statement is ambiguous"
err = model.Group(t.Columns().Id).Scan(&jobs)
```


### **Key Changes**

1. **Modified function signature**: `Group(groupBy ...string)` →
`Group(groupBy ...any)` to support Raw SQL expressions
2. **Auto-prefixing logic**: When JOINs are detected (by checking for "
JOIN " in the tables string), unqualified column names are automatically
prefixed with the primary table name
3. **Preserved existing behavior**: Already qualified columns
(containing ".") and Raw expressions are handled as before
4. **Added comprehensive test**: `Test_Model_Group_WithJoin` verifies
the fix works correctly with JOIN queries

---------

Co-authored-by: hailaz <739476267@qq.com>
2025-11-24 15:57:20 +08:00
54b7c249fd fix(os/gcfg): ignore fsnotify event error to avoid package gcfg totally failing (#4400)
问题描述: Windows 11
文件夹映射的网络驱动器里面的go项目在启动的时候会因为系统没有映射磁盘的文件事件监听而报错,从而导致整个项目启动失败,目前我临时的修复是将该错误改为警告进行打印

---------

Co-authored-by: anno <anno@anno.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: hailaz <739476267@qq.com>
2025-11-21 22:51:42 +08:00
126 changed files with 11153 additions and 5022 deletions

View File

@ -221,6 +221,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

0
.github/workflows/scripts/before_script.sh vendored Normal file → Executable file
View File

250
.github/workflows/scripts/ci-main-clean.sh vendored Executable file
View 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
View 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
View File

View File

@ -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"/>
@ -37,7 +38,7 @@ A powerful framework for faster, easier, and more efficient project development.
💖 [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.6" alt="goframe contributors"/>
</a>
## License

46
README.zh_CN.MD Normal file
View File

@ -0,0 +1,46 @@
[English](README.MD) | 简体中文
<div align=center>
<img src="https://goframe.org/img/logo_full.png" width="300" alt="goframe gf logo"/>
[![Go Reference](https://pkg.go.dev/badge/github.com/gogf/gf/v2.svg)](https://pkg.go.dev/github.com/gogf/gf/v2)
[![GoFrame CI](https://github.com/gogf/gf/actions/workflows/ci-main.yml/badge.svg)](https://github.com/gogf/gf/actions/workflows/ci-main.yml)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/gogf/gf/badge)](https://scorecard.dev/viewer/?uri=github.com/gogf/gf)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/9233/badge)](https://bestpractices.coreinfrastructure.org/projects/9233)
[![Go Report Card](https://goreportcard.com/badge/github.com/gogf/gf/v2)](https://goreportcard.com/report/github.com/gogf/gf/v2)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg?style=flat)](https://github.com/gogf/gf)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
[![Release](https://img.shields.io/github/v/release/gogf/gf?style=flat)](https://github.com/gogf/gf/releases)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/gogf/gf?style=flat)](https://github.com/gogf/gf/pulls)
[![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/gogf/gf?style=flat)](https://github.com/gogf/gf/pulls?q=is%3Apr+is%3Aclosed)
[![GitHub issues](https://img.shields.io/github/issues/gogf/gf?style=flat)](https://github.com/gogf/gf/issues)
[![GitHub closed issues](https://img.shields.io/github/issues-closed/gogf/gf?style=flat)](https://github.com/gogf/gf/issues?q=is%3Aissue+is%3Aclosed)
![Stars](https://img.shields.io/github/stars/gogf/gf?style=flat)
![Forks](https://img.shields.io/github/forks/gogf/gf?style=flat)
</div>
一个强大的框架,为了更快、更轻松、更高效的项目开发。
## 文档
- 官方网站: [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)
- GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
## 贡献者
💖 [感谢所有使 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% 免费和开源,永久保持。

View File

@ -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
View 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`。

View File

@ -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.6
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.6
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.6
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.6
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.6
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.6
github.com/gogf/gf/v2 v2.9.6
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

View File

@ -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=

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View 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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View 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())
}

View File

@ -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())
}

View 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...)
}

View File

@ -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
})
}

View File

@ -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)
})
}

File diff suppressed because it is too large Load Diff

View 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)
})
}

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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
}
}
}

View 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
})
}

View File

@ -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)

View File

@ -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)
}

View 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)
}

View File

@ -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)
})
}

View File

@ -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
View 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
}

View File

@ -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())

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View 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())
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View 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)
})
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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()
}

View 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
}

View 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
}

View 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
}

View File

@ -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)
}

View File

@ -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.6
)
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

View File

@ -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=

View File

@ -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.6
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

View File

@ -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=

View File

@ -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.6
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

View File

@ -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=

View File

@ -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.6
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

View File

@ -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=

View File

@ -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.6
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

View File

@ -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=

View File

@ -1,4 +1,4 @@
[简体中文](README.zh_CN.MD)
English | [简体中文](README.zh_CN.MD)
# Database drivers
@ -9,15 +9,15 @@ Powerful database drivers for package gdb.
Let's take `mysql` for example.
```shell
go get -u github.com/gogf/gf/contrib/drivers/mysql/v2
go get github.com/gogf/gf/contrib/drivers/mysql/v2@latest
# 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/clickhouse/v2@latest
go get github.com/gogf/gf/contrib/drivers/dm/v2@latest
go get github.com/gogf/gf/contrib/drivers/mssql/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
```
Choose and import the driver to your project:

View File

@ -1,3 +1,5 @@
[English](README.MD) | 简体中文
# 数据库驱动程序
用于gdb包的数据库驱动程序。
@ -7,15 +9,15 @@
以 `mysql` 为例。
```shell
go get -u github.com/gogf/gf/contrib/drivers/mysql/v2
go get github.com/gogf/gf/contrib/drivers/mysql/v2@latest
# 方便复制
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/clickhouse/v2@latest
go get github.com/gogf/gf/contrib/drivers/dm/v2@latest
go get github.com/gogf/gf/contrib/drivers/mssql/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
```
选择并将驱动程序导入到您的项目中:

View File

@ -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.6
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

View File

@ -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=

View File

@ -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

View File

@ -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_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
@ -60,9 +91,9 @@ func (d *Driver) TableFields(
Type: m["DATA_TYPE"].String(),
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

View File

@ -28,10 +28,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 +36,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 +49,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
}
@ -91,9 +88,6 @@ func TestTableFields(t *testing.T) {
"CREATED_TIME": {"TIMESTAMP", false},
}
_, err := dbErr.TableFields(ctx, "Fields")
gtest.AssertNE(err, nil)
res, err := db.TableFields(ctx, tables)
gtest.AssertNil(err)
@ -114,6 +108,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)
@ -153,7 +155,6 @@ func TestModelSave(t *testing.T) {
result sql.Result
err error
)
db.SetDebug(true)
result, err = db.Model(table).Data(g.Map{
"id": 1,

View File

@ -67,7 +67,6 @@ func init() {
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,11 @@ 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
}

View File

@ -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")
})
}

View 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")
})
}

View File

@ -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.6
)
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

View File

@ -6,8 +6,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=

View File

@ -3,14 +3,14 @@ module github.com/gogf/gf/contrib/drivers/mssql/v2
go 1.23.0
require (
github.com/gogf/gf/v2 v2.9.5
github.com/gogf/gf/v2 v2.9.6
github.com/microsoft/go-mssqldb v1.7.1
)
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

View File

@ -16,8 +16,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=

View File

@ -13,17 +13,24 @@ import (
)
const (
backIdInsertHeadDefault = "INSERT INTO"
backIdInsertHeadInsertIgnore = "INSERT IGNORE INTO"
// INSERT statement prefixes
insertPrefixDefault = "INSERT INTO"
insertPrefixIgnore = "INSERT IGNORE INTO"
autoIncrementName = "identity"
mssqlOutPutKey = "OUTPUT"
mssqlInsertedObjName = "INSERTED"
mssqlAffectFd = " 1 as AffectCount"
affectCountFieldName = "AffectCount"
mssqlPrimaryKeyName = "PRIMARY KEY"
fdId = "ID"
positionInsertValues = ") VALUES" // find the position of the string "VALUES" in the INSERT SQL statement to embed output code for retrieving the last inserted ID
// Database field attributes
fieldExtraIdentity = "IDENTITY"
fieldKeyPrimary = "PRI"
// SQL keywords and syntax markers
outputKeyword = "OUTPUT"
insertValuesMarker = ") VALUES" // find the position of the string "VALUES" in the INSERT SQL statement to embed output code for retrieving the last inserted ID
// Object and field references
insertedObjectName = "INSERTED"
// Result field names and aliases
affectCountExpression = " 1 as AffectCount"
lastInsertIdFieldAlias = "ID"
)
// DoExec commits the sql string and its arguments to underlying driver
@ -34,7 +41,7 @@ func (d *Driver) DoExec(ctx context.Context, link gdb.Link, sqlStr string, args
if tx := gdb.TXFromCtx(ctx, d.GetGroup()); tx != nil {
// Firstly, check and retrieve transaction link from context.
link = &txLinkMssql{tx.GetSqlTX()}
} else if link, err = d.Core.MasterLink(); err != nil {
} else if link, err = d.MasterLink(); err != nil {
// Or else it creates one from master node.
return nil, err
}
@ -46,17 +53,17 @@ func (d *Driver) DoExec(ctx context.Context, link gdb.Link, sqlStr string, args
}
// SQL filtering.
sqlStr, args = d.Core.FormatSqlBeforeExecuting(sqlStr, args)
sqlStr, args = d.FormatSqlBeforeExecuting(sqlStr, args)
sqlStr, args, err = d.DoFilter(ctx, link, sqlStr, args)
if err != nil {
return nil, err
}
if !(strings.HasPrefix(sqlStr, backIdInsertHeadDefault) || strings.HasPrefix(sqlStr, backIdInsertHeadInsertIgnore)) {
if !strings.HasPrefix(sqlStr, insertPrefixDefault) && !strings.HasPrefix(sqlStr, insertPrefixIgnore) {
return d.Core.DoExec(ctx, link, sqlStr, args)
}
// find the first pos
pos := strings.Index(sqlStr, positionInsertValues)
// Find the first position of VALUES marker in the INSERT statement.
pos := strings.Index(sqlStr, insertValuesMarker)
table := d.GetTableNameFromSql(sqlStr)
outPutSql := d.GetInsertOutputSql(ctx, table)
@ -82,21 +89,18 @@ func (d *Driver) DoExec(ctx context.Context, link gdb.Link, sqlStr string, args
if err != nil {
return &InsertResult{lastInsertId: 0, rowsAffected: 0, err: err}, err
}
var (
aCount int64 // affect count
lId int64 // last insert id
)
stdSqlResult := out.Records
if len(stdSqlResult) == 0 {
err = gerror.WrapCode(gcode.CodeDbOperationError, gerror.New("affectcount is zero"), `sql.Result.RowsAffected failed`)
return &InsertResult{lastInsertId: 0, rowsAffected: 0, err: err}, err
}
// get affect count
aCount = stdSqlResult[0].GMap().GetVar(affectCountFieldName).Int64()
// get last_insert_id
lId = stdSqlResult[0].GMap().GetVar(fdId).Int64()
// For batch insert, OUTPUT clause returns one row per inserted row.
// So the rowsAffected should be the count of returned records.
rowsAffected := int64(len(stdSqlResult))
// get last_insert_id from the first returned row
lastInsertId := stdSqlResult[0].GMap().GetVar(lastInsertIdFieldAlias).Int64()
return &InsertResult{lastInsertId: lId, rowsAffected: aCount}, err
return &InsertResult{lastInsertId: lastInsertId, rowsAffected: rowsAffected}, err
}
// GetTableNameFromSql get table name from sql statement
@ -112,7 +116,7 @@ func (d *Driver) GetTableNameFromSql(sqlStr string) (table string) {
pattern := "INTO(.+?)\\("
regCompile := regexp.MustCompile(pattern)
tableInfo := regCompile.FindStringSubmatch(sqlStr)
//get the first one. after the first it may be content of the value, it's not table name.
// get the first one. after the first it may be content of the value, it's not table name.
table = tableInfo[1]
table = strings.Trim(table, " ")
if strings.Contains(table, ".") {
@ -169,18 +173,18 @@ func (m *Driver) GetInsertOutputSql(ctx context.Context, table string) string {
return ""
}
extraSqlAry := make([]string, 0)
extraSqlAry = append(extraSqlAry, fmt.Sprintf(" %s %s", mssqlOutPutKey, mssqlAffectFd))
extraSqlAry = append(extraSqlAry, fmt.Sprintf(" %s %s", outputKeyword, affectCountExpression))
incrNo := 0
if len(fds) > 0 {
for _, fd := range fds {
// has primary key and is auto-increment
if fd.Extra == autoIncrementName && fd.Key == mssqlPrimaryKeyName && !fd.Null {
if fd.Extra == fieldExtraIdentity && fd.Key == fieldKeyPrimary && !fd.Null {
incrNoStr := ""
if incrNo == 0 { // fixed first field named id, convenient to get
incrNoStr = fmt.Sprintf(" as %s", fdId)
incrNoStr = fmt.Sprintf(" as %s", lastInsertIdFieldAlias)
}
extraSqlAry = append(extraSqlAry, fmt.Sprintf("%s.%s%s", mssqlInsertedObjName, fd.Name, incrNoStr))
extraSqlAry = append(extraSqlAry, fmt.Sprintf("%s.%s%s", insertedObjectName, fd.Name, incrNoStr))
incrNo++
}
// fmt.Printf("null:%t name:%s key:%s k:%s \n", fd.Null, fd.Name, fd.Key, k)

View File

@ -8,49 +8,48 @@ package mssql
import (
"context"
"reflect"
"testing"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/test/gtest"
)
func TestDriver_DoFilter(t *testing.T) {
type fields struct {
Core *gdb.Core
}
type args struct {
ctx context.Context
link gdb.Link
sql string
args []any
}
var tests []struct {
name string
fields fields
args args
wantNewSql string
wantNewArgs []any
wantErr bool
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Driver{
Core: tt.fields.Core,
}
gotNewSql, gotNewArgs, err := d.DoFilter(tt.args.ctx, tt.args.link, tt.args.sql, tt.args.args)
if (err != nil) != tt.wantErr {
t.Errorf("DoFilter() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotNewSql != tt.wantNewSql {
t.Errorf("DoFilter() gotNewSql = %v, want %v", gotNewSql, tt.wantNewSql)
}
if !reflect.DeepEqual(gotNewArgs, tt.wantNewArgs) {
t.Errorf("DoFilter() gotNewArgs = %v, want %v", gotNewArgs, tt.wantNewArgs)
}
})
}
gtest.C(t, func(t *gtest.T) {
d := &Driver{}
// Test SELECT with LIMIT
sql := "SELECT * FROM users WHERE id = ? LIMIT 10"
args := []any{1}
newSql, newArgs, err := d.DoFilter(context.Background(), nil, sql, args)
t.AssertNil(err)
t.Assert(newArgs, args)
// DoFilter should transform the SQL for MSSQL compatibility
t.AssertNE(newSql, "")
// Test INSERT statement (should remain unchanged except for placeholder)
sql = "INSERT INTO users (name) VALUES (?)"
args = []any{"test"}
newSql, newArgs, err = d.DoFilter(context.Background(), nil, sql, args)
t.AssertNil(err)
t.Assert(newArgs, args)
t.AssertNE(newSql, "")
// Test UPDATE statement
sql = "UPDATE users SET name = ? WHERE id = ?"
args = []any{"test", 1}
newSql, newArgs, err = d.DoFilter(context.Background(), nil, sql, args)
t.AssertNil(err)
t.Assert(newArgs, args)
t.AssertNE(newSql, "")
// Test DELETE statement
sql = "DELETE FROM users WHERE id = ?"
args = []any{1}
newSql, newArgs, err = d.DoFilter(context.Background(), nil, sql, args)
t.AssertNil(err)
t.Assert(newArgs, args)
t.AssertNE(newSql, "")
})
}
func TestDriver_handleSelectSqlReplacement(t *testing.T) {

View File

@ -17,32 +17,32 @@ import (
var (
tableFieldsSqlTmp = `
SELECT
a.name Field,
CASE b.name
WHEN 'datetime' THEN 'datetime'
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20), a.xprec) + ',' + convert(varchar(20), a.xscale) + ')'
WHEN 'char' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END AS Type,
CASE WHEN a.isnullable=1 THEN 'YES' ELSE 'NO' end AS [Null],
CASE WHEN exists (
SELECT 1 FROM sysobjects WHERE xtype='PK' AND name IN (
SELECT name FROM sysindexes WHERE indid IN (
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
)
)
) THEN 'PRI' ELSE '' END AS [Key],
CASE WHEN COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 THEN 'auto_increment' ELSE '' END Extra,
isnull(e.text,'') AS [Default],
isnull(g.[value],'') AS [Comment]
FROM syscolumns a
LEFT JOIN systypes b ON a.xtype=b.xtype AND a.xusertype=b.xusertype
INNER JOIN sysobjects d ON a.id=d.id AND d.xtype='U' AND d.name<>'dtproperties'
LEFT JOIN syscomments e ON a.cdefault=e.id
LEFT JOIN sys.extended_properties g ON a.id=g.major_id AND a.colid=g.minor_id
LEFT JOIN sys.extended_properties f ON d.id=f.major_id AND f.minor_id =0
WHERE d.name='%s'
ORDER BY a.id,a.colorder
c.name AS Field,
CASE
WHEN t.name IN ('datetime', 'datetime2', 'smalldatetime', 'date', 'time', 'text', 'ntext', 'image', 'xml') THEN t.name
WHEN t.name IN ('decimal', 'numeric') THEN t.name + '(' + CAST(c.precision AS varchar(20)) + ',' + CAST(c.scale AS varchar(20)) + ')'
WHEN t.name IN ('char', 'varchar', 'binary', 'varbinary') THEN t.name + '(' + CASE WHEN c.max_length = -1 THEN 'max' ELSE CAST(c.max_length AS varchar(20)) END + ')'
WHEN t.name IN ('nchar', 'nvarchar') THEN t.name + '(' + CASE WHEN c.max_length = -1 THEN 'max' ELSE CAST(c.max_length/2 AS varchar(20)) END + ')'
ELSE t.name
END AS Type,
CASE WHEN c.is_nullable = 1 THEN 'YES' ELSE 'NO' END AS [Null],
CASE WHEN pk.column_id IS NOT NULL THEN 'PRI' ELSE '' END AS [Key],
CASE WHEN c.is_identity = 1 THEN 'IDENTITY' ELSE '' END AS Extra,
ISNULL(dc.definition, '') AS [Default],
ISNULL(CAST(ep.value AS nvarchar(max)), '') AS [Comment]
FROM sys.columns c
INNER JOIN sys.objects o ON c.object_id = o.object_id AND o.type = 'U' AND o.is_ms_shipped = 0
INNER JOIN sys.types t ON c.user_type_id = t.user_type_id
LEFT JOIN sys.default_constraints dc ON c.default_object_id = dc.object_id
LEFT JOIN sys.extended_properties ep ON c.object_id = ep.major_id AND c.column_id = ep.minor_id AND ep.name = 'MS_Description'
LEFT JOIN (
SELECT ic.object_id, ic.column_id
FROM sys.index_columns ic
INNER JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id
WHERE i.is_primary_key = 1
) pk ON c.object_id = pk.object_id AND c.column_id = pk.column_id
WHERE o.name = '%s'
ORDER BY c.column_id
`
)

View File

@ -13,7 +13,7 @@ import (
)
const (
tablesSqlTmp = `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`
tablesSqlTmp = `SELECT name FROM sys.objects WHERE type='U' AND is_ms_shipped = 0 ORDER BY name`
)
// Tables retrieves and returns the tables of current schema.

View File

@ -46,7 +46,7 @@ func TestTables(t *testing.T) {
gtest.AssertEQ(find, true)
}
result, err = db.Tables(context.Background(), "master")
result, err = db.Tables(context.Background(), TestSchema)
gtest.AssertNil(err)
for i := 0; i < len(tables); i++ {
find := false
@ -92,7 +92,7 @@ func TestTableFields(t *testing.T) {
gtest.AssertEQ(res[k].Comment, v[5])
}
res, err = db.TableFields(context.Background(), "t_user", "master")
res, err = db.TableFields(context.Background(), "t_user", TestSchema)
gtest.AssertNil(err)
for k, v := range expect {

View File

@ -27,8 +27,7 @@ var (
const (
TableSize = 10
TableName = "t_user"
TestSchema1 = "test1"
TestSchema2 = "test2"
TestSchema = "test"
TableNamePrefix1 = "gf_"
TestDbUser = "sa"
TestDbPass = "LoremIpsum86"
@ -36,12 +35,40 @@ const (
)
func init() {
// First connect to master database to create test database
nodemaster := gdb.ConfigNode{
Host: "127.0.0.1",
Port: "1433",
User: TestDbUser,
Pass: TestDbPass,
Name: "master",
Type: "mssql",
Role: "master",
Charset: "utf8",
Weight: 1,
MaxIdleConnCount: 10,
MaxOpenConnCount: 10,
}
tempDb, err := gdb.New(nodemaster)
if err != nil {
gtest.Fatal(err)
}
// Create test database
if _, err := tempDb.Exec(context.Background(), fmt.Sprintf(`
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = '%s')
CREATE DATABASE [%s]
`, TestSchema, TestSchema)); err != nil {
gtest.Fatal(err)
}
node := gdb.ConfigNode{
Host: "127.0.0.1",
Port: "1433",
User: TestDbUser,
Pass: TestDbPass,
Name: "test",
Name: TestSchema,
Type: "mssql",
Role: "master",
Charset: "utf8",
@ -100,8 +127,8 @@ func createTable(table ...string) (name string) {
dropTable(name)
if _, err := db.Exec(context.Background(), fmt.Sprintf(`
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='%s' and xtype='U')
CREATE TABLE %s (
IF NOT EXISTS (SELECT * FROM sys.objects WHERE name='%s' and type='U')
CREATE TABLE [%s] (
ID numeric(10,0) NOT NULL,
PASSPORT VARCHAR(45) NULL,
PASSWORD VARCHAR(32) NULL,
@ -114,7 +141,6 @@ func createTable(table ...string) (name string) {
gtest.Fatal(err)
}
db.Schema("test")
return
}
@ -141,18 +167,18 @@ func createInitTable(table ...string) (name string) {
func dropTable(table string) {
if _, err := db.Exec(context.Background(), fmt.Sprintf(`
IF EXISTS (SELECT * FROM sysobjects WHERE name='%s' and xtype='U')
DROP TABLE %s
IF EXISTS (SELECT * FROM sys.objects WHERE name='%s' and type='U')
DROP TABLE [%s]
`, table, table)); err != nil {
gtest.Fatal(err)
}
}
// createInsertAndGetIdTableForTest test for InsertAndGetId
// createInsertAndGetIdTableForTest tests InsertAndGetId functionality
func createInsertAndGetIdTableForTest() (name string) {
if _, err := db.Exec(context.Background(), `
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='ip_to_id' and xtype='U')
IF NOT EXISTS (SELECT * FROM sys.objects WHERE name='ip_to_id' and type='U')
begin
CREATE TABLE [ip_to_id](
[id] [int] IDENTITY(1,1) NOT NULL,

View File

@ -29,21 +29,21 @@ func Test_Page(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Page(1, 2).Order("id").All()
t.AssertNil(err)
fmt.Println("page:1--------", result)
// fmt.Println("page:1--------", result)
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["ID"], 1)
gtest.Assert(result[1]["ID"], 2)
result, err = db.Model(table).Page(2, 2).Order("id").All()
t.AssertNil(err)
fmt.Println("page: 2--------", result)
// fmt.Println("page: 2--------", result)
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["ID"], 3)
gtest.Assert(result[1]["ID"], 4)
result, err = db.Model(table).Page(3, 2).Order("id").All()
t.AssertNil(err)
fmt.Println("page:3 --------", result)
// fmt.Println("page:3 --------", result)
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["ID"], 5)
@ -61,7 +61,6 @@ func Test_Model_Insert(t *testing.T) {
user := db.Model(table)
result, err := user.Data(g.Map{
"id": 1,
"uid": 1,
"passport": "t1",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_1",
@ -71,7 +70,6 @@ func Test_Model_Insert(t *testing.T) {
result, err = db.Model(table).Data(g.Map{
"id": "2",
"uid": "2",
"passport": "t2",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_2",
@ -81,7 +79,6 @@ func Test_Model_Insert(t *testing.T) {
type User struct {
Id int `gconv:"id"`
Uid int `gconv:"uid"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
@ -90,7 +87,6 @@ func Test_Model_Insert(t *testing.T) {
// Model inserting.
result, err = db.Model(table).Data(User{
Id: 3,
Uid: 3,
Passport: "t3",
Password: "25d55ad283aa400af464c76d713c07ad",
Nickname: "name_3",
@ -103,7 +99,6 @@ func Test_Model_Insert(t *testing.T) {
result, err = db.Model(table).Data(&User{
Id: 4,
Uid: 4,
Passport: "t4",
Password: "25d55ad283aa400af464c76d713c07ad",
Nickname: "T4",
@ -213,7 +208,6 @@ func Test_Model_BatchInsertWithArrayStruct(t *testing.T) {
for i := 1; i <= TableSize; i++ {
array.Append(g.Map{
"id": i,
"uid": i,
"passport": fmt.Sprintf("t%d", i),
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": fmt.Sprintf("name_%d", i),
@ -235,7 +229,6 @@ func Test_Model_Batch(t *testing.T) {
_, err := db.Model(table).Data(g.List{
{
"id": 2,
"uid": 2,
"passport": "t2",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_2",
@ -243,7 +236,6 @@ func Test_Model_Batch(t *testing.T) {
},
{
"id": 3,
"uid": 3,
"passport": "t3",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_3",
@ -1607,7 +1599,6 @@ func Test_Model_Option_Where(t *testing.T) {
n, _ := r.RowsAffected()
t.Assert(n, TableSize)
})
return
gtest.C(t, func(t *gtest.T) {
table := createInitTable()
defer dropTable(table)
@ -2677,3 +2668,82 @@ func Test_Model_Replace(t *testing.T) {
t.Assert(err, "Replace operation is not supported by mssql driver")
})
}
// Test_Model_Insert_RowsAffected tests the RowsAffected result for INSERT operations.
// This test ensures that the rowsAffected value is correctly returned from the database,
// especially for batch INSERT statements.
func Test_Model_Insert_RowsAffected(t *testing.T) {
table := createTable()
defer dropTable(table)
// Test single insert - rowsAffected should be 1
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Data(g.Map{
"id": 1,
"passport": "user_1",
"password": "pass_1",
"nickname": "name_1",
"create_time": gtime.Now().String(),
}).Insert()
t.AssertNil(err)
n, err := result.RowsAffected()
t.AssertNil(err)
t.Assert(n, 1)
})
// Test batch insert with 3 rows - rowsAffected should be 3
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Data(g.List{
{
"id": 2,
"passport": "user_2",
"password": "pass_2",
"nickname": "name_2",
"create_time": gtime.Now().String(),
},
{
"id": 3,
"passport": "user_3",
"password": "pass_3",
"nickname": "name_3",
"create_time": gtime.Now().String(),
},
{
"id": 4,
"passport": "user_4",
"password": "pass_4",
"nickname": "name_4",
"create_time": gtime.Now().String(),
},
}).Insert()
t.AssertNil(err)
n, err := result.RowsAffected()
t.AssertNil(err)
t.Assert(n, 3)
})
// Test batch insert with 5 rows - rowsAffected should be 5
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Data(g.List{
{"id": 5, "passport": "user_5", "password": "pass_5", "nickname": "name_5", "create_time": gtime.Now().String()},
{"id": 6, "passport": "user_6", "password": "pass_6", "nickname": "name_6", "create_time": gtime.Now().String()},
{"id": 7, "passport": "user_7", "password": "pass_7", "nickname": "name_7", "create_time": gtime.Now().String()},
{"id": 8, "passport": "user_8", "password": "pass_8", "nickname": "name_8", "create_time": gtime.Now().String()},
{"id": 9, "passport": "user_9", "password": "pass_9", "nickname": "name_9", "create_time": gtime.Now().String()},
}).Insert()
t.AssertNil(err)
n, err := result.RowsAffected()
t.AssertNil(err)
t.Assert(n, 5)
})
// Verify total count in table
gtest.C(t, func(t *gtest.T) {
count, err := db.Model(table).Count()
t.AssertNil(err)
t.Assert(count, 9)
})
}

View File

@ -4,13 +4,13 @@ go 1.23.0
require (
github.com/go-sql-driver/mysql v1.7.1
github.com/gogf/gf/v2 v2.9.5
github.com/gogf/gf/v2 v2.9.6
)
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

View File

@ -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=

View File

@ -0,0 +1,236 @@
// 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 mysql_test
import (
"testing"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/test/gtest"
)
// Test_Model_Group_WithJoin tests GROUP BY with JOIN queries
func Test_Model_Group_WithJoin(t *testing.T) {
var (
table1 = gtime.TimestampNanoStr() + "_user"
table2 = gtime.TimestampNanoStr() + "_user_detail"
)
createInitTable(table1)
defer dropTable(table1)
createInitTable(table2)
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
// Test basic GROUP BY with JOIN - unqualified column should be auto-prefixed
// This prevents "Column 'id' in group statement is ambiguous" error
r, err := db.Model(table1+" u").
Fields("u.id", "u.nickname", "COUNT(*) as count").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Group("id").
Order("u.id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
// Test GROUP BY with already qualified column
r, err = db.Model(table1+" u").
Fields("u.id", "u.nickname", "COUNT(*) as count").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Group("u.id").
Order("u.id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
// Test GROUP BY with multiple columns
r, err = db.Model(table1+" u").
Fields("u.id", "u.nickname", "COUNT(*) as count").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Group("id", "nickname").
Order("u.id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
// Test GROUP BY with Raw expression
r, err = db.Model(table1+" u").
Fields("u.id", "u.nickname", "COUNT(*) as count").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Group(gdb.Raw("u.id")).
Order("u.id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
// Test GROUP BY on non-primary table should work correctly
r, err = db.Model(table1+" u").
Fields("ud.id", "COUNT(*) as count").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Group("ud.id").
Order("ud.id asc").All()
t.AssertNil(err)
// Should have results from the joined table
t.Assert(len(r) > 0, true)
})
}
// Test_Model_Order_WithJoin tests ORDER BY with JOIN queries
func Test_Model_Order_WithJoin(t *testing.T) {
var (
table1 = gtime.TimestampNanoStr() + "_user"
table2 = gtime.TimestampNanoStr() + "_user_detail"
)
createInitTable(table1)
defer dropTable(table1)
createInitTable(table2)
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
// Test ORDER BY with JOIN - unqualified column should be auto-prefixed
r, err := db.Model(table1+" u").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Order("id desc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "2")
t.Assert(r[1]["id"], "1")
// Test ORDER BY with already qualified column
r, err = db.Model(table1+" u").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Order("u.id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
// Test ORDER BY with Raw expression
r, err = db.Model(table1+" u").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Order(gdb.Raw("u.id asc")).All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
// Test multiple ORDER BY clauses with JOIN
r, err = db.Model(table1+" u").
LeftJoin(table2+" ud", "u.id = ud.id").
Order("id asc").Order("nickname asc").All()
t.AssertNil(err)
t.Assert(len(r) > 0, true)
// Test ORDER BY with asc/desc keywords
r, err = db.Model(table1+" u").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Order("id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
})
}
// Test_Model_Group_And_Order_WithJoin tests combined GROUP BY and ORDER BY with JOINs
func Test_Model_Group_And_Order_WithJoin(t *testing.T) {
var (
table1 = gtime.TimestampNanoStr() + "_user"
table2 = gtime.TimestampNanoStr() + "_user_detail"
)
createInitTable(table1)
defer dropTable(table1)
createInitTable(table2)
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
// Test combined GROUP BY and ORDER BY with JOIN
r, err := db.Model(table1+" u").
Fields("u.id", "COUNT(*) as count").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Group("id").
Order("id desc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "2")
t.Assert(r[1]["id"], "1")
// Test with already qualified GROUP BY and unqualified ORDER BY
r, err = db.Model(table1+" u").
Fields("u.id", "COUNT(*) as count").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Group("u.id").
Order("id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
// Test with unqualified GROUP BY and qualified ORDER BY
r, err = db.Model(table1+" u").
Fields("u.id", "COUNT(*) as count").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Group("id").
Order("u.id desc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "2")
t.Assert(r[1]["id"], "1")
// Test with both unqualified
r, err = db.Model(table1+" u").
Fields("u.id", "COUNT(*) as count").
LeftJoin(table2+" ud", "u.id = ud.id").
Where("u.id", g.Slice{1, 2}).
Group("id").
Order("id").All()
t.AssertNil(err)
t.Assert(len(r), 2)
})
}
// Test_Model_Join_Without_Alias tests JOIN without table aliases
func Test_Model_Join_Without_Alias(t *testing.T) {
var (
table1 = gtime.TimestampNanoStr() + "_user"
table2 = gtime.TimestampNanoStr() + "_user_detail"
)
createInitTable(table1)
defer dropTable(table1)
createInitTable(table2)
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
// Test GROUP BY and ORDER BY with JOIN but without aliases
// This should still work correctly
r, err := db.Model(table1).
Fields(table1+".id", "COUNT(*) as count").
LeftJoin(table2, table1+".id = "+table2+".id").
Where(table1+".id", g.Slice{1, 2}).
Group(table1 + ".id").
Order(table1 + ".id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
})
}

View File

@ -3783,6 +3783,7 @@ func Test_Model_FixGdbJoin(t *testing.T) {
FieldsPrefix(`rules_template`, "name").
FieldsPrefix(`common_resource`, `src_instance_id`, "database_kind", "source_type", "ip", "port")
all, err := orm.OrderAsc("src_instance_id").All()
t.Assert(err, nil)
t.Assert(len(all), 4)
t.Assert(all[0]["pay_mode"], 1)
t.Assert(all[0]["src_instance_id"], 2)

View File

@ -3,14 +3,14 @@ module github.com/gogf/gf/contrib/drivers/oracle/v2
go 1.23.0
require (
github.com/gogf/gf/v2 v2.9.5
github.com/gogf/gf/v2 v2.9.6
github.com/sijms/go-ora/v2 v2.7.10
)
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

View File

@ -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=

View File

@ -3,14 +3,14 @@ module github.com/gogf/gf/contrib/drivers/pgsql/v2
go 1.23.0
require (
github.com/gogf/gf/v2 v2.9.5
github.com/gogf/gf/v2 v2.9.6
github.com/lib/pq v1.10.9
)
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

View File

@ -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=

View File

@ -4,14 +4,14 @@ go 1.23.0
require (
github.com/glebarez/go-sqlite v1.21.2
github.com/gogf/gf/v2 v2.9.5
github.com/gogf/gf/v2 v2.9.6
)
require (
github.com/BurntSushi/toml v1.5.0 // 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/go-logr/logr v1.4.3 // indirect

View File

@ -6,8 +6,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=

View File

@ -3,14 +3,14 @@ module github.com/gogf/gf/contrib/drivers/sqlitecgo/v2
go 1.23.0
require (
github.com/gogf/gf/v2 v2.9.5
github.com/gogf/gf/v2 v2.9.6
github.com/mattn/go-sqlite3 v1.14.17
)
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

View File

@ -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=

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/metric/otelmetric/v2
go 1.23.0
require (
github.com/gogf/gf/v2 v2.9.5
github.com/gogf/gf/v2 v2.9.6
github.com/prometheus/client_golang v1.23.2
go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0
go.opentelemetry.io/otel v1.38.0
@ -18,7 +18,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.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

View File

@ -8,8 +8,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=

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/nosql/redis/v2
go 1.23.0
require (
github.com/gogf/gf/v2 v2.9.5
github.com/gogf/gf/v2 v2.9.6
github.com/redis/go-redis/v9 v9.12.1
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/trace v1.38.0
@ -14,7 +14,7 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // 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

View File

@ -12,8 +12,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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
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=

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/registry/consul/v2
go 1.23.0
require (
github.com/gogf/gf/v2 v2.9.5
github.com/gogf/gf/v2 v2.9.6
github.com/hashicorp/consul/api v1.26.1
)
@ -11,7 +11,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

Some files were not shown because too many files have changed in this diff Show More