Compare commits

..

7 Commits

164 changed files with 5560 additions and 15891 deletions

View File

@ -54,7 +54,7 @@ jobs:
# Service containers to run with `code-test`
services:
# Etcd service.
# docker run -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnamilegacy/etcd:3.4.24
# docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnamilegacy/etcd:3.4.24
etcd:
image: bitnamilegacy/etcd:3.4.24
env:
@ -75,7 +75,7 @@ jobs:
- 6379:6379
# MySQL backend server.
# docker run \
# docker run -d --name mysql \
# -p 3306:3306 \
# -e MYSQL_DATABASE=test \
# -e MYSQL_ROOT_PASSWORD=12345678 \
@ -89,7 +89,7 @@ jobs:
- 3306:3306
# MariaDb backend server.
# docker run \
# docker run -d --name mariadb \
# -p 3307:3306 \
# -e MYSQL_DATABASE=test \
# -e MYSQL_ROOT_PASSWORD=12345678 \
@ -103,7 +103,7 @@ jobs:
- 3307:3306
# PostgreSQL backend server.
# docker run \
# docker run -d --name postgres \
# -p 5432:5432 \
# -e POSTGRES_PASSWORD=12345678 \
# -e POSTGRES_USER=postgres \
@ -150,7 +150,7 @@ jobs:
--health-retries 10
# ClickHouse backend server.
# docker run \
# docker run -d --name clickhouse \
# -p 9000:9000 -p 8123:8123 -p 9001:9001 \
# clickhouse/clickhouse-server:24.11.1.2557-alpine
clickhouse-server:
@ -161,7 +161,7 @@ jobs:
- 9001:9001
# Polaris backend server.
# docker run \
# docker run -d --name polaris \
# -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \
# polarismesh/polaris-standalone:v1.17.2
polaris:
@ -221,12 +221,6 @@ 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

View File

@ -17,12 +17,11 @@ jobs:
steps:
- name: Checkout Github Code
uses: actions/checkout@v5
- name: Set Up Golang Environment
uses: actions/setup-go@v5
with:
go-version: 1.25
cache: false
- name: Build CLI Binary
run: |

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

View File

@ -1,250 +0,0 @@
#!/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 Executable file → Normal file
View File

@ -6,54 +6,56 @@ 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 [[ $dirpath = "." ]]; then
# # No space left on device error sometimes occurs in CI pipelines, so clean the cache before tests.
# go clean -cache
# 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
cd $dirpath
go mod tidy
go build ./...
# test with coverage
if [ "${coverage}" = "coverage" ]; then
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
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
else
go test ./... -count=1 -race || exit 1
go test ./... -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 Executable file → Normal file
View File

View File

@ -1,4 +1,3 @@
English | [简体中文](README.zh_CN.MD)
<div align=center>
<img src="https://goframe.org/img/logo_full.png" width="300" alt="goframe gf logo"/>
@ -38,7 +37,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.6" alt="goframe contributors"/>
<img src="https://goframe.org/img/contributors.svg?version=v2.9.5" alt="goframe contributors"/>
</a>
## License

View File

@ -1,46 +0,0 @@
[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,5 +1,3 @@
English | [简体中文](README.zh_CN.MD)
# gf
`gf` is a powerful CLI tool for building [GoFrame](https://goframe.org) application with convenience.
@ -23,18 +21,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 | yes | - |
| 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 | 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. |
## 2) Manually Install
@ -45,31 +43,30 @@ go install github.com/gogf/gf/cmd/gf/v2@v2.5.5 # certain version(should be >= v2
## 2. Commands
```shell
$ gf -h
```html
$ gf
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
doc download https://pages.goframe.org/ to run locally
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
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.

View File

@ -1,82 +0,0 @@
[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.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/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/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/v2 v2.0.0-alpha // indirect
github.com/emirpasic/gods v1.18.1 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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,20 @@ 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.6 h1:rJzRmA5TGWMeKDebdDosYODoUrMUHqfA5pWO1MBC5b0=
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.6/go.mod h1:u+bUsuftf8qpKpPZPdOFhzh3F5KQzo6Wqa9JFTCLFqg=
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.6 h1:3QTlIbSdrVYvRMNUF6nckspA6Eh5Uy2NqwB3/auxIwk=
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.6/go.mod h1:oMteYgkWImPpUVe1aqPKtZ8jX1dG3v60lS7IA87MwFQ=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.6 h1:BY1ThxMo0bTx2P18PuCe57ARmjHuEithSdob/CbH/rw=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.6/go.mod h1:v/jKO9JJdLctlPlnUSnnG0SNSEpElM51Qx3KoI5crkU=
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.6 h1:12+sWI/hm1D4KxG+1FMZpfoU3PwtSLJ9KbLNa20roLg=
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.6/go.mod h1:gjjhgxqjafnORK0F4Fa5W8TJlassw7svKy7RFj5GKss=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.6 h1:LG/bTOJEpyNu6+IdREqFyi6J8LdZIeceeyxhuyV58LQ=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.6/go.mod h1:Ekd5IgUGyBlbfqKD/69hkIL9vHF6F4V2FeEP3h/pH08=
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.6 h1:3QZvWIlz3dLjNELQU+5ZZZWuzEx9gsRFLU+qIKVUG6M=
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.6/go.mod h1:7EEAe8UYI5dLeuwCWN3HgC62OhjIYbkynaoavw1U/k4=
github.com/gogf/gf/v2 v2.9.6 h1:fQ6uPtS1Ra8qY+OuzPPZTlgksJ4eOXmTZ1/a2l3Idog=
github.com/gogf/gf/v2 v2.9.6/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
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

@ -104,10 +104,6 @@ var (
"smallmoney": {
Type: "float64",
},
"uuid": {
Type: "uuid.UUID",
Import: "github.com/google/uuid",
},
}
// tablewriter Options

View File

@ -4,7 +4,7 @@ go 1.23.0
toolchain go1.24.6
require github.com/gogf/gf/v2 v2.9.6
require github.com/gogf/gf/v2 v2.9.5
require (
go.opentelemetry.io/otel v1.38.0 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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

@ -7,143 +7,245 @@
package gmap
import (
"sync"
"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"
)
// AnyAnyMap wraps map type `map[any]any` and provides more map features.
type AnyAnyMap struct {
*KVMap[any, any]
once sync.Once
mu rwmutex.RWMutex
data map[any]any
}
// 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 {
m := &AnyAnyMap{
KVMap: NewKVMap[any, any](safe...),
return &AnyAnyMap{
mu: rwmutex.Create(safe...),
data: make(map[any]any),
}
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 {
m := &AnyAnyMap{
KVMap: NewKVMapFrom(data, safe...),
return &AnyAnyMap{
mu: rwmutex.Create(safe...),
data: data,
}
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) {
m.lazyInit()
m.KVMap.Iterator(f)
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 *AnyAnyMap) Clone(safe ...bool) *AnyAnyMap {
m.lazyInit()
return NewAnyAnyMapFrom(m.MapCopy(), safe...)
return NewFrom(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.lazyInit()
return m.KVMap.Map()
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
}
// MapCopy returns a shallow copy of the underlying data of the hash map.
func (m *AnyAnyMap) MapCopy() map[any]any {
m.lazyInit()
return m.KVMap.MapCopy()
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
}
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
func (m *AnyAnyMap) MapStrAny() map[string]any {
m.lazyInit()
return m.KVMap.MapStrAny()
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 *AnyAnyMap) FilterEmpty() {
m.lazyInit()
m.KVMap.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 *AnyAnyMap) FilterNil() {
m.lazyInit()
m.KVMap.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 *AnyAnyMap) Set(key any, value any) {
m.lazyInit()
m.KVMap.Set(key, value)
m.mu.Lock()
if m.data == nil {
m.data = make(map[any]any)
}
m.data[key] = value
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *AnyAnyMap) Sets(data map[any]any) {
m.lazyInit()
m.KVMap.Sets(data)
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 *AnyAnyMap) Search(key any) (value any, found bool) {
m.lazyInit()
return m.KVMap.Search(key)
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 *AnyAnyMap) Get(key any) (value any) {
m.lazyInit()
return m.KVMap.Get(key)
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 *AnyAnyMap) Pop() (key, value any) {
m.lazyInit()
return m.KVMap.Pop()
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 *AnyAnyMap) Pops(size int) map[any]any {
m.lazyInit()
return m.KVMap.Pops(size)
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
}
// 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 {
m.lazyInit()
return m.KVMap.GetOrSet(key, value)
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 *AnyAnyMap) GetOrSetFunc(key any, f func() any) any {
m.lazyInit()
return m.KVMap.GetOrSetFunc(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
@ -153,50 +255,55 @@ 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 {
m.lazyInit()
return m.KVMap.GetOrSetFuncLock(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// 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 {
m.lazyInit()
return m.KVMap.GetVar(key)
return gvar.New(m.Get(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 {
m.lazyInit()
return m.KVMap.GetVarOrSet(key, value)
return gvar.New(m.GetOrSet(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 {
m.lazyInit()
return m.KVMap.GetVarOrSetFunc(key, f)
return gvar.New(m.GetOrSetFunc(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 {
m.lazyInit()
return m.KVMap.GetVarOrSetFuncLock(key, f)
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 *AnyAnyMap) SetIfNotExist(key any, value any) bool {
m.lazyInit()
return m.KVMap.SetIfNotExist(key, value)
if !m.Contains(key) {
m.doSetWithLockCheck(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 *AnyAnyMap) SetIfNotExistFunc(key any, f func() any) bool {
m.lazyInit()
return m.KVMap.SetIfNotExistFunc(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
@ -205,76 +312,119 @@ 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 {
m.lazyInit()
return m.KVMap.SetIfNotExistFuncLock(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// Remove deletes value from map by given `key`, and return this deleted value.
func (m *AnyAnyMap) Remove(key any) (value any) {
m.lazyInit()
return m.KVMap.Remove(key)
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 *AnyAnyMap) Removes(keys []any) {
m.lazyInit()
m.KVMap.Removes(keys)
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 *AnyAnyMap) Keys() []any {
m.lazyInit()
return m.KVMap.Keys()
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
}
// Values returns all values of the map as a slice.
func (m *AnyAnyMap) Values() []any {
m.lazyInit()
return m.KVMap.Values()
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
}
// Contains checks whether a key exists.
// It returns true if the `key` exists, or else false.
func (m *AnyAnyMap) Contains(key any) bool {
m.lazyInit()
return m.KVMap.Contains(key)
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 *AnyAnyMap) Size() int {
m.lazyInit()
return m.KVMap.Size()
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 *AnyAnyMap) IsEmpty() bool {
m.lazyInit()
return m.KVMap.IsEmpty()
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *AnyAnyMap) Clear() {
m.lazyInit()
m.KVMap.Clear()
m.mu.Lock()
m.data = make(map[any]any)
m.mu.Unlock()
}
// Replace the data of the map with given `data`.
func (m *AnyAnyMap) Replace(data map[any]any) {
m.lazyInit()
m.KVMap.Replace(data)
m.mu.Lock()
m.data = data
m.mu.Unlock()
}
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
func (m *AnyAnyMap) LockFunc(f func(m map[any]any)) {
m.lazyInit()
m.KVMap.LockFunc(f)
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
func (m *AnyAnyMap) RLockFunc(f func(m map[any]any)) {
m.lazyInit()
m.KVMap.RLockFunc(f)
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
@ -291,8 +441,19 @@ 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.lazyInit()
m.KVMap.Merge(other.KVMap)
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.
@ -300,40 +461,79 @@ func (m *AnyAnyMap) String() string {
if m == nil {
return ""
}
m.lazyInit()
return m.KVMap.String()
b, _ := m.MarshalJSON()
return string(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m AnyAnyMap) MarshalJSON() ([]byte, error) {
m.lazyInit()
return m.KVMap.MarshalJSON()
return json.Marshal(gconv.Map(m.Map()))
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *AnyAnyMap) UnmarshalJSON(b []byte) error {
m.lazyInit()
return m.KVMap.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *AnyAnyMap) UnmarshalValue(value any) (err error) {
m.lazyInit()
return m.KVMap.UnmarshalValue(value)
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
}
// DeepCopy implements interface for deep copy of current type.
func (m *AnyAnyMap) DeepCopy() any {
m.lazyInit()
return &AnyAnyMap{
KVMap: m.KVMap.DeepCopy().(*KVMap[any, any]),
if m == nil {
return nil
}
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 {
m.lazyInit()
return m.KVMap.IsSubOf(other.KVMap)
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
}
// Diff compares current map `m` with map `other` and returns their different keys.
@ -341,6 +541,22 @@ 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.lazyInit()
return m.KVMap.Diff(other.KVMap)
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,143 +8,244 @@
package gmap
import (
"sync"
"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"
)
// IntAnyMap implements map[int]any with RWMutex that has switch.
type IntAnyMap struct {
*KVMap[int, any]
once sync.Once
mu rwmutex.RWMutex
data map[int]any
}
// 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 {
m := &IntAnyMap{
KVMap: NewKVMap[int, any](safe...),
return &IntAnyMap{
mu: rwmutex.Create(safe...),
data: make(map[int]any),
}
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 {
m := &IntAnyMap{
KVMap: NewKVMapFrom(data, safe...),
return &IntAnyMap{
mu: rwmutex.Create(safe...),
data: data,
}
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) {
m.lazyInit()
m.KVMap.Iterator(f)
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 *IntAnyMap) Clone(safe ...bool) *IntAnyMap {
m.lazyInit()
return NewIntAnyMapFrom(m.MapCopy(), safe...)
func (m *IntAnyMap) Clone() *IntAnyMap {
return NewIntAnyMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// 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.lazyInit()
return m.KVMap.Map()
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
}
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
func (m *IntAnyMap) MapStrAny() map[string]any {
m.lazyInit()
return m.KVMap.MapStrAny()
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
}
// MapCopy returns a copy of the underlying data of the hash map.
func (m *IntAnyMap) MapCopy() map[int]any {
m.lazyInit()
return m.KVMap.MapCopy()
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
}
// 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.lazyInit()
m.KVMap.FilterEmpty()
m.mu.Lock()
for k, v := range m.data {
if empty.IsEmpty(v) {
delete(m.data, k)
}
}
m.mu.Unlock()
}
// FilterNil deletes all key-value pair of which the value is nil.
func (m *IntAnyMap) FilterNil() {
m.lazyInit()
m.KVMap.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 *IntAnyMap) Set(key int, val any) {
m.lazyInit()
m.KVMap.Set(key, val)
m.mu.Lock()
if m.data == nil {
m.data = make(map[int]any)
}
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *IntAnyMap) Sets(data map[int]any) {
m.lazyInit()
m.KVMap.Sets(data)
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 *IntAnyMap) Search(key int) (value any, found bool) {
m.lazyInit()
return m.KVMap.Search(key)
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 *IntAnyMap) Get(key int) (value any) {
m.lazyInit()
return m.KVMap.Get(key)
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 *IntAnyMap) Pop() (key int, value any) {
m.lazyInit()
return m.KVMap.Pop()
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 *IntAnyMap) Pops(size int) map[int]any {
m.lazyInit()
return m.KVMap.Pops(size)
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
}
// 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 {
m.lazyInit()
return m.KVMap.GetOrSet(key, value)
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 returns this value.
func (m *IntAnyMap) GetOrSetFunc(key int, f func() any) any {
m.lazyInit()
return m.KVMap.GetOrSetFunc(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
@ -153,50 +254,55 @@ 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 {
m.lazyInit()
return m.KVMap.GetOrSetFuncLock(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// 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 {
m.lazyInit()
return m.KVMap.GetVar(key)
return gvar.New(m.Get(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 {
m.lazyInit()
return m.KVMap.GetVarOrSet(key, value)
return gvar.New(m.GetOrSet(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 {
m.lazyInit()
return m.KVMap.GetVarOrSetFunc(key, f)
return gvar.New(m.GetOrSetFunc(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 {
m.lazyInit()
return m.KVMap.GetVarOrSetFuncLock(key, f)
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 *IntAnyMap) SetIfNotExist(key int, value any) bool {
m.lazyInit()
return m.KVMap.SetIfNotExist(key, value)
if !m.Contains(key) {
m.doSetWithLockCheck(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 *IntAnyMap) SetIfNotExistFunc(key int, f func() any) bool {
m.lazyInit()
return m.KVMap.SetIfNotExistFunc(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
@ -205,76 +311,119 @@ 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 {
m.lazyInit()
return m.KVMap.SetIfNotExistFuncLock(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// Removes batch deletes values of the map by keys.
func (m *IntAnyMap) Removes(keys []int) {
m.lazyInit()
m.KVMap.Removes(keys)
m.mu.Lock()
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given `key`, and return this deleted value.
func (m *IntAnyMap) Remove(key int) (value any) {
m.lazyInit()
return m.KVMap.Remove(key)
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
}
// Keys returns all keys of the map as a slice.
func (m *IntAnyMap) Keys() []int {
m.lazyInit()
return m.KVMap.Keys()
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
}
// Values returns all values of the map as a slice.
func (m *IntAnyMap) Values() []any {
m.lazyInit()
return m.KVMap.Values()
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
}
// Contains checks whether a key exists.
// It returns true if the `key` exists, or else false.
func (m *IntAnyMap) Contains(key int) bool {
m.lazyInit()
return m.KVMap.Contains(key)
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 *IntAnyMap) Size() int {
m.lazyInit()
return m.KVMap.Size()
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 *IntAnyMap) IsEmpty() bool {
m.lazyInit()
return m.KVMap.IsEmpty()
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *IntAnyMap) Clear() {
m.lazyInit()
m.KVMap.Clear()
m.mu.Lock()
m.data = make(map[int]any)
m.mu.Unlock()
}
// Replace the data of the map with given `data`.
func (m *IntAnyMap) Replace(data map[int]any) {
m.lazyInit()
m.KVMap.Replace(data)
m.mu.Lock()
m.data = data
m.mu.Unlock()
}
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
func (m *IntAnyMap) LockFunc(f func(m map[int]any)) {
m.lazyInit()
m.KVMap.LockFunc(f)
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
func (m *IntAnyMap) RLockFunc(f func(m map[int]any)) {
m.lazyInit()
m.KVMap.RLockFunc(f)
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
@ -291,8 +440,19 @@ 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.lazyInit()
m.KVMap.Merge(other.KVMap)
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.
@ -300,40 +460,81 @@ func (m *IntAnyMap) String() string {
if m == nil {
return ""
}
m.lazyInit()
return m.KVMap.String()
b, _ := m.MarshalJSON()
return string(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m IntAnyMap) MarshalJSON() ([]byte, error) {
m.lazyInit()
return m.KVMap.MarshalJSON()
m.mu.RLock()
defer m.mu.RUnlock()
return json.Marshal(m.data)
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *IntAnyMap) UnmarshalJSON(b []byte) error {
m.lazyInit()
return m.KVMap.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *IntAnyMap) UnmarshalValue(value any) (err error) {
m.lazyInit()
return m.KVMap.UnmarshalValue(value)
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
}
// DeepCopy implements interface for deep copy of current type.
func (m *IntAnyMap) DeepCopy() any {
m.lazyInit()
return &IntAnyMap{
KVMap: m.KVMap.DeepCopy().(*KVMap[int, any]),
if m == nil {
return nil
}
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 {
m.lazyInit()
return m.KVMap.IsSubOf(other.KVMap)
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
}
// Diff compares current map `m` with map `other` and returns their different keys.
@ -341,6 +542,22 @@ 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.lazyInit()
return m.KVMap.Diff(other.KVMap)
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

@ -6,12 +6,17 @@
package gmap
import "sync"
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"
)
// IntIntMap implements map[int]int with RWMutex that has switch.
type IntIntMap struct {
*KVMap[int, int]
once sync.Once
mu rwmutex.RWMutex
data map[int]int
}
// NewIntIntMap returns an empty IntIntMap object.
@ -19,7 +24,8 @@ type IntIntMap struct {
// which is false in default.
func NewIntIntMap(safe ...bool) *IntIntMap {
return &IntIntMap{
KVMap: NewKVMap[int, int](safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]int),
}
}
@ -28,109 +34,193 @@ 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{
KVMap: NewKVMapFrom(data, safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// 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) {
m.lazyInit()
m.KVMap.Iterator(f)
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 *IntIntMap) Clone(safe ...bool) *IntIntMap {
m.lazyInit()
return &IntIntMap{KVMap: m.KVMap.Clone(safe...)}
func (m *IntIntMap) Clone() *IntIntMap {
return NewIntIntMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// 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.lazyInit()
return m.KVMap.Map()
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
}
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
func (m *IntIntMap) MapStrAny() map[string]any {
m.lazyInit()
return m.KVMap.MapStrAny()
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
}
// MapCopy returns a copy of the underlying data of the hash map.
func (m *IntIntMap) MapCopy() map[int]int {
m.lazyInit()
return m.KVMap.MapCopy()
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
}
// 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.lazyInit()
m.KVMap.FilterEmpty()
m.mu.Lock()
for k, v := range m.data {
if empty.IsEmpty(v) {
delete(m.data, k)
}
}
m.mu.Unlock()
}
// Set sets key-value to the hash map.
func (m *IntIntMap) Set(key int, val int) {
m.lazyInit()
m.KVMap.Set(key, val)
m.mu.Lock()
if m.data == nil {
m.data = make(map[int]int)
}
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *IntIntMap) Sets(data map[int]int) {
m.lazyInit()
m.KVMap.Sets(data)
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 *IntIntMap) Search(key int) (value int, found bool) {
m.lazyInit()
return m.KVMap.Search(key)
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 *IntIntMap) Get(key int) (value int) {
m.lazyInit()
return m.KVMap.Get(key)
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 *IntIntMap) Pop() (key, value int) {
m.lazyInit()
return m.KVMap.Pop()
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 *IntIntMap) Pops(size int) map[int]int {
m.lazyInit()
return m.KVMap.Pops(size)
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
}
// 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 {
m.lazyInit()
return m.KVMap.GetOrSet(key, value)
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 returns this value.
func (m *IntIntMap) GetOrSetFunc(key int, f func() int) int {
m.lazyInit()
return m.KVMap.GetOrSetFunc(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
@ -139,22 +229,41 @@ 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 {
m.lazyInit()
return m.KVMap.GetOrSetFuncLock(key, f)
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
}
}
// 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 {
m.lazyInit()
return m.KVMap.SetIfNotExist(key, value)
if !m.Contains(key) {
m.doSetWithLockCheck(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 *IntIntMap) SetIfNotExistFunc(key int, f func() int) bool {
m.lazyInit()
return m.KVMap.SetIfNotExistFunc(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
@ -163,76 +272,126 @@ 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 {
m.lazyInit()
return m.KVMap.SetIfNotExistFuncLock(key, f)
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
}
// Removes batch deletes values of the map by keys.
func (m *IntIntMap) Removes(keys []int) {
m.lazyInit()
m.KVMap.Removes(keys)
m.mu.Lock()
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given `key`, and return this deleted value.
func (m *IntIntMap) Remove(key int) (value int) {
m.lazyInit()
return m.KVMap.Remove(key)
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
}
// Keys returns all keys of the map as a slice.
func (m *IntIntMap) Keys() []int {
m.lazyInit()
return m.KVMap.Keys()
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
}
// Values returns all values of the map as a slice.
func (m *IntIntMap) Values() []int {
m.lazyInit()
return m.KVMap.Values()
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
}
// Contains checks whether a key exists.
// It returns true if the `key` exists, or else false.
func (m *IntIntMap) Contains(key int) bool {
m.lazyInit()
return m.KVMap.Contains(key)
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 *IntIntMap) Size() int {
m.lazyInit()
return m.KVMap.Size()
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 *IntIntMap) IsEmpty() bool {
m.lazyInit()
return m.KVMap.IsEmpty()
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *IntIntMap) Clear() {
m.lazyInit()
m.KVMap.Clear()
m.mu.Lock()
m.data = make(map[int]int)
m.mu.Unlock()
}
// Replace the data of the map with given `data`.
func (m *IntIntMap) Replace(data map[int]int) {
m.lazyInit()
m.KVMap.Replace(data)
m.mu.Lock()
m.data = data
m.mu.Unlock()
}
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
func (m *IntIntMap) LockFunc(f func(m map[int]int)) {
m.lazyInit()
m.KVMap.LockFunc(f)
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
func (m *IntIntMap) RLockFunc(f func(m map[int]int)) {
m.lazyInit()
m.KVMap.RLockFunc(f)
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
@ -249,8 +408,19 @@ 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.lazyInit()
m.KVMap.Merge(other.KVMap)
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.
@ -258,40 +428,81 @@ func (m *IntIntMap) String() string {
if m == nil {
return ""
}
m.lazyInit()
return m.KVMap.String()
b, _ := m.MarshalJSON()
return string(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m IntIntMap) MarshalJSON() ([]byte, error) {
m.lazyInit()
return m.KVMap.MarshalJSON()
m.mu.RLock()
defer m.mu.RUnlock()
return json.Marshal(m.data)
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *IntIntMap) UnmarshalJSON(b []byte) error {
m.lazyInit()
return m.KVMap.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *IntIntMap) UnmarshalValue(value any) (err error) {
m.lazyInit()
return m.KVMap.UnmarshalValue(value)
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
}
// DeepCopy implements interface for deep copy of current type.
func (m *IntIntMap) DeepCopy() any {
m.lazyInit()
return &IntIntMap{
KVMap: m.KVMap.DeepCopy().(*KVMap[int, int]),
if m == nil {
return nil
}
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 {
m.lazyInit()
return m.KVMap.IsSubOf(other.KVMap)
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
}
// Diff compares current map `m` with map `other` and returns their different keys.
@ -299,6 +510,22 @@ 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.lazyInit()
return m.KVMap.Diff(other.KVMap)
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
}

View File

@ -7,15 +7,16 @@
package gmap
import (
"sync"
"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"
)
// IntStrMap implements map[int]string with RWMutex that has switch.
type IntStrMap struct {
*KVMap[int, string]
once sync.Once
mu rwmutex.RWMutex
data map[int]string
}
// NewIntStrMap returns an empty IntStrMap object.
@ -23,7 +24,8 @@ type IntStrMap struct {
// which is false in default.
func NewIntStrMap(safe ...bool) *IntStrMap {
return &IntStrMap{
KVMap: NewKVMap[int, string](safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]string),
}
}
@ -32,109 +34,193 @@ 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{
KVMap: NewKVMapFrom(data, safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// 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) {
m.lazyInit()
m.KVMap.Iterator(f)
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 *IntStrMap) Clone(safe ...bool) *IntStrMap {
m.lazyInit()
return &IntStrMap{KVMap: m.KVMap.Clone(safe...)}
func (m *IntStrMap) Clone() *IntStrMap {
return NewIntStrMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// 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.lazyInit()
return m.KVMap.Map()
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
}
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
func (m *IntStrMap) MapStrAny() map[string]any {
m.lazyInit()
return m.KVMap.MapStrAny()
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
}
// MapCopy returns a copy of the underlying data of the hash map.
func (m *IntStrMap) MapCopy() map[int]string {
m.lazyInit()
return m.KVMap.MapCopy()
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
}
// 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.lazyInit()
m.KVMap.FilterEmpty()
m.mu.Lock()
for k, v := range m.data {
if empty.IsEmpty(v) {
delete(m.data, k)
}
}
m.mu.Unlock()
}
// Set sets key-value to the hash map.
func (m *IntStrMap) Set(key int, val string) {
m.lazyInit()
m.KVMap.Set(key, val)
m.mu.Lock()
if m.data == nil {
m.data = make(map[int]string)
}
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *IntStrMap) Sets(data map[int]string) {
m.lazyInit()
m.KVMap.Sets(data)
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 *IntStrMap) Search(key int) (value string, found bool) {
m.lazyInit()
return m.KVMap.Search(key)
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 *IntStrMap) Get(key int) (value string) {
m.lazyInit()
return m.KVMap.Get(key)
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 *IntStrMap) Pop() (key int, value string) {
m.lazyInit()
return m.KVMap.Pop()
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 *IntStrMap) Pops(size int) map[int]string {
m.lazyInit()
return m.KVMap.Pops(size)
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
}
// 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 {
m.lazyInit()
return m.KVMap.GetOrSet(key, value)
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 returns this value.
func (m *IntStrMap) GetOrSetFunc(key int, f func() string) string {
m.lazyInit()
return m.KVMap.GetOrSetFunc(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
@ -143,22 +229,41 @@ 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 {
m.lazyInit()
return m.KVMap.GetOrSetFuncLock(key, f)
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
}
}
// 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 {
m.lazyInit()
return m.KVMap.SetIfNotExist(key, value)
if !m.Contains(key) {
m.doSetWithLockCheck(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 *IntStrMap) SetIfNotExistFunc(key int, f func() string) bool {
m.lazyInit()
return m.KVMap.SetIfNotExistFunc(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
@ -167,76 +272,126 @@ 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 {
m.lazyInit()
return m.KVMap.SetIfNotExistFuncLock(key, f)
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
}
// Removes batch deletes values of the map by keys.
func (m *IntStrMap) Removes(keys []int) {
m.lazyInit()
m.KVMap.Removes(keys)
m.mu.Lock()
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given `key`, and return this deleted value.
func (m *IntStrMap) Remove(key int) (value string) {
m.lazyInit()
return m.KVMap.Remove(key)
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
}
// Keys returns all keys of the map as a slice.
func (m *IntStrMap) Keys() []int {
m.lazyInit()
return m.KVMap.Keys()
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
}
// Values returns all values of the map as a slice.
func (m *IntStrMap) Values() []string {
m.lazyInit()
return m.KVMap.Values()
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
}
// Contains checks whether a key exists.
// It returns true if the `key` exists, or else false.
func (m *IntStrMap) Contains(key int) bool {
m.lazyInit()
return m.KVMap.Contains(key)
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 *IntStrMap) Size() int {
m.lazyInit()
return m.KVMap.Size()
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 *IntStrMap) IsEmpty() bool {
m.lazyInit()
return m.KVMap.IsEmpty()
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *IntStrMap) Clear() {
m.lazyInit()
m.KVMap.Clear()
m.mu.Lock()
m.data = make(map[int]string)
m.mu.Unlock()
}
// Replace the data of the map with given `data`.
func (m *IntStrMap) Replace(data map[int]string) {
m.lazyInit()
m.KVMap.Replace(data)
m.mu.Lock()
m.data = data
m.mu.Unlock()
}
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
func (m *IntStrMap) LockFunc(f func(m map[int]string)) {
m.lazyInit()
m.KVMap.LockFunc(f)
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
func (m *IntStrMap) RLockFunc(f func(m map[int]string)) {
m.lazyInit()
m.KVMap.RLockFunc(f)
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
@ -253,8 +408,19 @@ 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.lazyInit()
m.KVMap.Merge(other.KVMap)
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.
@ -262,40 +428,81 @@ func (m *IntStrMap) String() string {
if m == nil {
return ""
}
m.lazyInit()
return m.KVMap.String()
b, _ := m.MarshalJSON()
return string(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m IntStrMap) MarshalJSON() ([]byte, error) {
m.lazyInit()
return m.KVMap.MarshalJSON()
m.mu.RLock()
defer m.mu.RUnlock()
return json.Marshal(m.data)
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *IntStrMap) UnmarshalJSON(b []byte) error {
m.lazyInit()
return m.KVMap.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *IntStrMap) UnmarshalValue(value any) (err error) {
m.lazyInit()
return m.KVMap.UnmarshalValue(value)
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
}
// DeepCopy implements interface for deep copy of current type.
func (m *IntStrMap) DeepCopy() any {
m.lazyInit()
return &IntStrMap{
KVMap: m.KVMap.DeepCopy().(*KVMap[int, string]),
if m == nil {
return nil
}
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 {
m.lazyInit()
return m.KVMap.IsSubOf(other.KVMap)
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
}
// Diff compares current map `m` with map `other` and returns their different keys.
@ -303,6 +510,22 @@ 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.lazyInit()
return m.KVMap.Diff(other.KVMap)
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
}

View File

@ -1,582 +0,0 @@
// 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,66 +8,71 @@
package gmap
import (
"sync"
"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"
)
// StrAnyMap implements map[string]any with RWMutex that has switch.
type StrAnyMap struct {
*KVMap[string, any]
once sync.Once
mu rwmutex.RWMutex
data map[string]any
}
// 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 {
m := &StrAnyMap{
KVMap: NewKVMap[string, any](safe...),
return &StrAnyMap{
mu: rwmutex.Create(safe...),
data: make(map[string]any),
}
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 {
m := &StrAnyMap{
KVMap: NewKVMapFrom(data, safe...),
return &StrAnyMap{
mu: rwmutex.Create(safe...),
data: data,
}
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) {
m.lazyInit()
m.KVMap.Iterator(f)
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 *StrAnyMap) Clone(safe ...bool) *StrAnyMap {
m.lazyInit()
return NewStrAnyMapFrom(m.MapCopy(), safe...)
func (m *StrAnyMap) Clone() *StrAnyMap {
return NewStrAnyMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// 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.lazyInit()
return m.KVMap.Map()
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
}
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
@ -77,74 +82,165 @@ 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.lazyInit()
return m.KVMap.MapCopy()
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
}
// 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.lazyInit()
m.KVMap.FilterEmpty()
m.mu.Lock()
for k, v := range m.data {
if empty.IsEmpty(v) {
delete(m.data, k)
}
}
m.mu.Unlock()
}
// FilterNil deletes all key-value pair of which the value is nil.
func (m *StrAnyMap) FilterNil() {
m.lazyInit()
m.KVMap.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 *StrAnyMap) Set(key string, val any) {
m.lazyInit()
m.KVMap.Set(key, val)
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]any)
}
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *StrAnyMap) Sets(data map[string]any) {
m.lazyInit()
m.KVMap.Sets(data)
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 *StrAnyMap) Search(key string) (value any, found bool) {
m.lazyInit()
return m.KVMap.Search(key)
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 *StrAnyMap) Get(key string) (value any) {
m.lazyInit()
return m.KVMap.Get(key)
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 *StrAnyMap) Pop() (key string, value any) {
m.lazyInit()
return m.KVMap.Pop()
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 *StrAnyMap) Pops(size int) map[string]any {
m.lazyInit()
return m.KVMap.Pops(size)
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
}
// 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 {
m.lazyInit()
return m.KVMap.GetOrSet(key, value)
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 *StrAnyMap) GetOrSetFunc(key string, f func() any) any {
m.lazyInit()
return m.KVMap.GetOrSetFunc(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
@ -154,50 +250,55 @@ 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 {
m.lazyInit()
return m.KVMap.GetOrSetFuncLock(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// 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 {
m.lazyInit()
return m.KVMap.GetVar(key)
return gvar.New(m.Get(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 {
m.lazyInit()
return m.KVMap.GetVarOrSet(key, value)
return gvar.New(m.GetOrSet(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 {
m.lazyInit()
return m.KVMap.GetVarOrSetFunc(key, f)
return gvar.New(m.GetOrSetFunc(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 {
m.lazyInit()
return m.KVMap.GetVarOrSetFuncLock(key, f)
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 *StrAnyMap) SetIfNotExist(key string, value any) bool {
m.lazyInit()
return m.KVMap.SetIfNotExist(key, value)
if !m.Contains(key) {
m.doSetWithLockCheck(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 *StrAnyMap) SetIfNotExistFunc(key string, f func() any) bool {
m.lazyInit()
return m.KVMap.SetIfNotExistFunc(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
@ -206,76 +307,119 @@ 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 {
m.lazyInit()
return m.KVMap.SetIfNotExistFuncLock(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// Removes batch deletes values of the map by keys.
func (m *StrAnyMap) Removes(keys []string) {
m.lazyInit()
m.KVMap.Removes(keys)
m.mu.Lock()
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given `key`, and return this deleted value.
func (m *StrAnyMap) Remove(key string) (value any) {
m.lazyInit()
return m.KVMap.Remove(key)
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
}
// Keys returns all keys of the map as a slice.
func (m *StrAnyMap) Keys() []string {
m.lazyInit()
return m.KVMap.Keys()
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
}
// Values returns all values of the map as a slice.
func (m *StrAnyMap) Values() []any {
m.lazyInit()
return m.KVMap.Values()
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
}
// Contains checks whether a key exists.
// It returns true if the `key` exists, or else false.
func (m *StrAnyMap) Contains(key string) bool {
m.lazyInit()
return m.KVMap.Contains(key)
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 *StrAnyMap) Size() int {
m.lazyInit()
return m.KVMap.Size()
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 *StrAnyMap) IsEmpty() bool {
m.lazyInit()
return m.KVMap.IsEmpty()
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *StrAnyMap) Clear() {
m.lazyInit()
m.KVMap.Clear()
m.mu.Lock()
m.data = make(map[string]any)
m.mu.Unlock()
}
// Replace the data of the map with given `data`.
func (m *StrAnyMap) Replace(data map[string]any) {
m.lazyInit()
m.KVMap.Replace(data)
m.mu.Lock()
m.data = data
m.mu.Unlock()
}
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
func (m *StrAnyMap) LockFunc(f func(m map[string]any)) {
m.lazyInit()
m.KVMap.LockFunc(f)
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
func (m *StrAnyMap) RLockFunc(f func(m map[string]any)) {
m.lazyInit()
m.KVMap.RLockFunc(f)
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
@ -292,8 +436,19 @@ 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.lazyInit()
m.KVMap.Merge(other.KVMap)
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.
@ -301,40 +456,71 @@ func (m *StrAnyMap) String() string {
if m == nil {
return ""
}
m.lazyInit()
return m.KVMap.String()
b, _ := m.MarshalJSON()
return string(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m StrAnyMap) MarshalJSON() ([]byte, error) {
m.lazyInit()
return m.KVMap.MarshalJSON()
m.mu.RLock()
defer m.mu.RUnlock()
return json.Marshal(m.data)
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *StrAnyMap) UnmarshalJSON(b []byte) error {
m.lazyInit()
return m.KVMap.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *StrAnyMap) UnmarshalValue(value any) (err error) {
m.lazyInit()
return m.KVMap.UnmarshalValue(value)
m.mu.Lock()
defer m.mu.Unlock()
m.data = gconv.Map(value)
return
}
// DeepCopy implements interface for deep copy of current type.
func (m *StrAnyMap) DeepCopy() any {
m.lazyInit()
return &StrAnyMap{
KVMap: m.KVMap.DeepCopy().(*KVMap[string, any]),
if m == nil {
return nil
}
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 {
m.lazyInit()
return m.KVMap.IsSubOf(other.KVMap)
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
}
// Diff compares current map `m` with map `other` and returns their different keys.
@ -342,6 +528,22 @@ 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.lazyInit()
return m.KVMap.Diff(other.KVMap)
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,15 +8,16 @@
package gmap
import (
"sync"
"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"
)
// StrIntMap implements map[string]int with RWMutex that has switch.
type StrIntMap struct {
*KVMap[string, int]
once sync.Once
mu rwmutex.RWMutex
data map[string]int
}
// NewStrIntMap returns an empty StrIntMap object.
@ -24,7 +25,8 @@ type StrIntMap struct {
// which is false in default.
func NewStrIntMap(safe ...bool) *StrIntMap {
return &StrIntMap{
KVMap: NewKVMap[string, int](safe...),
mu: rwmutex.Create(safe...),
data: make(map[string]int),
}
}
@ -33,110 +35,195 @@ 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{
KVMap: NewKVMapFrom(data, safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// 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) {
m.lazyInit()
m.KVMap.Iterator(f)
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 *StrIntMap) Clone(safe ...bool) *StrIntMap {
m.lazyInit()
return &StrIntMap{KVMap: m.KVMap.Clone(safe...)}
func (m *StrIntMap) Clone() *StrIntMap {
return NewStrIntMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// 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.lazyInit()
return m.KVMap.Map()
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
}
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
func (m *StrIntMap) MapStrAny() map[string]any {
m.lazyInit()
return m.KVMap.MapStrAny()
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
}
// MapCopy returns a copy of the underlying data of the hash map.
func (m *StrIntMap) MapCopy() map[string]int {
m.lazyInit()
return m.KVMap.MapCopy()
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
}
// 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.lazyInit()
m.KVMap.FilterEmpty()
m.mu.Lock()
for k, v := range m.data {
if empty.IsEmpty(v) {
delete(m.data, k)
}
}
m.mu.Unlock()
}
// Set sets key-value to the hash map.
func (m *StrIntMap) Set(key string, val int) {
m.lazyInit()
m.KVMap.Set(key, val)
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]int)
}
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *StrIntMap) Sets(data map[string]int) {
m.lazyInit()
m.KVMap.Sets(data)
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 *StrIntMap) Search(key string) (value int, found bool) {
m.lazyInit()
return m.KVMap.Search(key)
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 *StrIntMap) Get(key string) (value int) {
m.lazyInit()
return m.KVMap.Get(key)
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 *StrIntMap) Pop() (key string, value int) {
m.lazyInit()
return m.KVMap.Pop()
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 *StrIntMap) Pops(size int) map[string]int {
m.lazyInit()
return m.KVMap.Pops(size)
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
}
// 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 {
m.lazyInit()
return m.KVMap.GetOrSet(key, value)
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 *StrIntMap) GetOrSetFunc(key string, f func() int) int {
m.lazyInit()
return m.KVMap.GetOrSetFunc(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
@ -146,22 +233,41 @@ 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 {
m.lazyInit()
return m.KVMap.GetOrSetFuncLock(key, f)
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
}
}
// 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 {
m.lazyInit()
return m.KVMap.SetIfNotExist(key, value)
if !m.Contains(key) {
m.doSetWithLockCheck(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 *StrIntMap) SetIfNotExistFunc(key string, f func() int) bool {
m.lazyInit()
return m.KVMap.SetIfNotExistFunc(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
@ -170,76 +276,126 @@ 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 {
m.lazyInit()
return m.KVMap.SetIfNotExistFuncLock(key, f)
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
}
// Removes batch deletes values of the map by keys.
func (m *StrIntMap) Removes(keys []string) {
m.lazyInit()
m.KVMap.Removes(keys)
m.mu.Lock()
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given `key`, and return this deleted value.
func (m *StrIntMap) Remove(key string) (value int) {
m.lazyInit()
return m.KVMap.Remove(key)
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
}
// Keys returns all keys of the map as a slice.
func (m *StrIntMap) Keys() []string {
m.lazyInit()
return m.KVMap.Keys()
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
}
// Values returns all values of the map as a slice.
func (m *StrIntMap) Values() []int {
m.lazyInit()
return m.KVMap.Values()
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
}
// Contains checks whether a key exists.
// It returns true if the `key` exists, or else false.
func (m *StrIntMap) Contains(key string) bool {
m.lazyInit()
return m.KVMap.Contains(key)
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 *StrIntMap) Size() int {
m.lazyInit()
return m.KVMap.Size()
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 *StrIntMap) IsEmpty() bool {
m.lazyInit()
return m.KVMap.IsEmpty()
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *StrIntMap) Clear() {
m.lazyInit()
m.KVMap.Clear()
m.mu.Lock()
m.data = make(map[string]int)
m.mu.Unlock()
}
// Replace the data of the map with given `data`.
func (m *StrIntMap) Replace(data map[string]int) {
m.lazyInit()
m.KVMap.Replace(data)
m.mu.Lock()
m.data = data
m.mu.Unlock()
}
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
func (m *StrIntMap) LockFunc(f func(m map[string]int)) {
m.lazyInit()
m.KVMap.LockFunc(f)
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
func (m *StrIntMap) RLockFunc(f func(m map[string]int)) {
m.lazyInit()
m.KVMap.RLockFunc(f)
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
@ -256,8 +412,19 @@ 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.lazyInit()
m.KVMap.Merge(other.KVMap)
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.
@ -265,40 +432,81 @@ func (m *StrIntMap) String() string {
if m == nil {
return ""
}
m.lazyInit()
return m.KVMap.String()
b, _ := m.MarshalJSON()
return string(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m StrIntMap) MarshalJSON() ([]byte, error) {
m.lazyInit()
return m.KVMap.MarshalJSON()
m.mu.RLock()
defer m.mu.RUnlock()
return json.Marshal(m.data)
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *StrIntMap) UnmarshalJSON(b []byte) error {
m.lazyInit()
return m.KVMap.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *StrIntMap) UnmarshalValue(value any) (err error) {
m.lazyInit()
return m.KVMap.UnmarshalValue(value)
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
}
// DeepCopy implements interface for deep copy of current type.
func (m *StrIntMap) DeepCopy() any {
m.lazyInit()
return &StrIntMap{
KVMap: m.KVMap.DeepCopy().(*KVMap[string, int]),
if m == nil {
return nil
}
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 {
m.lazyInit()
return m.KVMap.IsSubOf(other.KVMap)
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
}
// Diff compares current map `m` with map `other` and returns their different keys.
@ -306,6 +514,22 @@ 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.lazyInit()
return m.KVMap.Diff(other.KVMap)
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
}

View File

@ -7,12 +7,17 @@
package gmap
import "sync"
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"
)
// StrStrMap implements map[string]string with RWMutex that has switch.
type StrStrMap struct {
*KVMap[string, string]
once sync.Once
mu rwmutex.RWMutex
data map[string]string
}
// NewStrStrMap returns an empty StrStrMap object.
@ -20,7 +25,8 @@ type StrStrMap struct {
// which is false in default.
func NewStrStrMap(safe ...bool) *StrStrMap {
return &StrStrMap{
KVMap: NewKVMap[string, string](safe...),
data: make(map[string]string),
mu: rwmutex.Create(safe...),
}
}
@ -29,110 +35,194 @@ 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{
KVMap: NewKVMapFrom(data, safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// 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) {
m.lazyInit()
m.KVMap.Iterator(f)
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 *StrStrMap) Clone(safe ...bool) *StrStrMap {
m.lazyInit()
return &StrStrMap{KVMap: m.KVMap.Clone(safe...)}
func (m *StrStrMap) Clone() *StrStrMap {
return NewStrStrMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// 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.lazyInit()
return m.KVMap.Map()
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
}
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
func (m *StrStrMap) MapStrAny() map[string]any {
m.lazyInit()
return m.KVMap.MapStrAny()
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
}
// MapCopy returns a copy of the underlying data of the hash map.
func (m *StrStrMap) MapCopy() map[string]string {
m.lazyInit()
return m.KVMap.MapCopy()
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
}
// 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.lazyInit()
m.KVMap.FilterEmpty()
m.mu.Lock()
for k, v := range m.data {
if empty.IsEmpty(v) {
delete(m.data, k)
}
}
m.mu.Unlock()
}
// Set sets key-value to the hash map.
func (m *StrStrMap) Set(key string, val string) {
m.lazyInit()
m.KVMap.Set(key, val)
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]string)
}
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *StrStrMap) Sets(data map[string]string) {
m.lazyInit()
m.KVMap.Sets(data)
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 *StrStrMap) Search(key string) (value string, found bool) {
m.lazyInit()
return m.KVMap.Search(key)
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 *StrStrMap) Get(key string) (value string) {
m.lazyInit()
return m.KVMap.Get(key)
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 *StrStrMap) Pop() (key, value string) {
m.lazyInit()
return m.KVMap.Pop()
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 *StrStrMap) Pops(size int) map[string]string {
m.lazyInit()
return m.KVMap.Pops(size)
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
}
// 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 {
m.lazyInit()
return m.KVMap.GetOrSet(key, value)
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 *StrStrMap) GetOrSetFunc(key string, f func() string) string {
m.lazyInit()
return m.KVMap.GetOrSetFunc(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
@ -142,22 +232,41 @@ 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 {
m.lazyInit()
return m.KVMap.GetOrSetFuncLock(key, f)
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
}
}
// 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 {
m.lazyInit()
return m.KVMap.SetIfNotExist(key, value)
if !m.Contains(key) {
m.doSetWithLockCheck(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 *StrStrMap) SetIfNotExistFunc(key string, f func() string) bool {
m.lazyInit()
return m.KVMap.SetIfNotExistFunc(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
@ -166,76 +275,126 @@ 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 {
m.lazyInit()
return m.KVMap.SetIfNotExistFuncLock(key, f)
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
}
// Removes batch deletes values of the map by keys.
func (m *StrStrMap) Removes(keys []string) {
m.lazyInit()
m.KVMap.Removes(keys)
m.mu.Lock()
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given `key`, and return this deleted value.
func (m *StrStrMap) Remove(key string) (value string) {
m.lazyInit()
return m.KVMap.Remove(key)
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
}
// Keys returns all keys of the map as a slice.
func (m *StrStrMap) Keys() []string {
m.lazyInit()
return m.KVMap.Keys()
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
}
// Values returns all values of the map as a slice.
func (m *StrStrMap) Values() []string {
m.lazyInit()
return m.KVMap.Values()
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
}
// Contains checks whether a key exists.
// It returns true if the `key` exists, or else false.
func (m *StrStrMap) Contains(key string) bool {
m.lazyInit()
return m.KVMap.Contains(key)
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 *StrStrMap) Size() int {
m.lazyInit()
return m.KVMap.Size()
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 *StrStrMap) IsEmpty() bool {
m.lazyInit()
return m.KVMap.IsEmpty()
return m.Size() == 0
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *StrStrMap) Clear() {
m.lazyInit()
m.KVMap.Clear()
m.mu.Lock()
m.data = make(map[string]string)
m.mu.Unlock()
}
// Replace the data of the map with given `data`.
func (m *StrStrMap) Replace(data map[string]string) {
m.lazyInit()
m.KVMap.Replace(data)
m.mu.Lock()
m.data = data
m.mu.Unlock()
}
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
func (m *StrStrMap) LockFunc(f func(m map[string]string)) {
m.lazyInit()
m.KVMap.LockFunc(f)
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
func (m *StrStrMap) RLockFunc(f func(m map[string]string)) {
m.lazyInit()
m.KVMap.RLockFunc(f)
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
@ -252,8 +411,19 @@ 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.lazyInit()
m.KVMap.Merge(other.KVMap)
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.
@ -261,40 +431,71 @@ func (m *StrStrMap) String() string {
if m == nil {
return ""
}
m.lazyInit()
return m.KVMap.String()
b, _ := m.MarshalJSON()
return string(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m StrStrMap) MarshalJSON() ([]byte, error) {
m.lazyInit()
return m.KVMap.MarshalJSON()
m.mu.RLock()
defer m.mu.RUnlock()
return json.Marshal(m.data)
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *StrStrMap) UnmarshalJSON(b []byte) error {
m.lazyInit()
return m.KVMap.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *StrStrMap) UnmarshalValue(value any) (err error) {
m.lazyInit()
return m.KVMap.UnmarshalValue(value)
m.mu.Lock()
defer m.mu.Unlock()
m.data = gconv.MapStrStr(value)
return
}
// DeepCopy implements interface for deep copy of current type.
func (m *StrStrMap) DeepCopy() any {
m.lazyInit()
return &StrStrMap{
KVMap: m.KVMap.DeepCopy().(*KVMap[string, string]),
if m == nil {
return nil
}
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 {
m.lazyInit()
return m.KVMap.IsSubOf(other.KVMap)
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
}
// Diff compares current map `m` with map `other` and returns their different keys.
@ -302,6 +503,22 @@ 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.lazyInit()
return m.KVMap.Diff(other.KVMap)
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
}

View File

@ -1,654 +0,0 @@
// 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,9 +7,15 @@
package gmap
import (
"sync"
"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"
)
@ -21,11 +27,15 @@ import (
//
// Reference: http://en.wikipedia.org/wiki/Associative_array
type ListMap struct {
*ListKVMap[any, any]
once sync.Once
mu rwmutex.RWMutex
data map[any]*glist.Element
list *glist.List
}
type gListMapNode = gListKVMapNode[any, any]
type gListMapNode struct {
key any
value any
}
// NewListMap returns an empty link map.
// ListMap is backed by a hash table to store values and doubly-linked list to store ordering.
@ -33,7 +43,9 @@ type gListMapNode = gListKVMapNode[any, any]
// which is false in default.
func NewListMap(safe ...bool) *ListMap {
return &ListMap{
ListKVMap: NewListKVMap[any, any](safe...),
mu: rwmutex.Create(safe...),
data: make(map[any]*glist.Element),
list: glist.New(),
}
}
@ -46,15 +58,6 @@ 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)
@ -63,15 +66,29 @@ 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.lazyInit()
m.ListKVMap.IteratorAsc(f)
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)
})
}
}
// 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.lazyInit()
m.ListKVMap.IteratorDesc(f)
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)
})
}
}
// Clone returns a new link map with copy of current map data.
@ -81,85 +98,232 @@ 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.lazyInit()
m.ListKVMap.Clear()
m.mu.Lock()
m.data = make(map[any]*glist.Element)
m.list = glist.New()
m.mu.Unlock()
}
// Replace the data of the map with given `data`.
func (m *ListMap) Replace(data map[any]any) {
m.lazyInit()
m.ListKVMap.Replace(data)
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()
}
// Map returns a copy of the underlying data of the map.
func (m *ListMap) Map() map[any]any {
m.lazyInit()
return m.ListKVMap.Map()
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
}
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
func (m *ListMap) MapStrAny() map[string]any {
m.lazyInit()
return m.ListKVMap.MapStrAny()
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
}
// FilterEmpty deletes all key-value pair of which the value is empty.
func (m *ListMap) FilterEmpty() {
m.lazyInit()
m.ListKVMap.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()
}
// Set sets key-value to the map.
func (m *ListMap) Set(key any, value any) {
m.lazyInit()
m.ListKVMap.Set(key, value)
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()
}
// Sets batch sets key-values to the map.
func (m *ListMap) Sets(data map[any]any) {
m.lazyInit()
m.ListKVMap.Sets(data)
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()
}
// 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.lazyInit()
return m.ListKVMap.Search(key)
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
}
// Get returns the value by given `key`.
func (m *ListMap) Get(key any) (value any) {
m.lazyInit()
return m.ListKVMap.Get(key)
m.mu.RLock()
if m.data != nil {
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
}
}
m.mu.RUnlock()
return
}
// Pop retrieves and deletes an item from the map.
func (m *ListMap) Pop() (key, value any) {
m.lazyInit()
return m.ListKVMap.Pop()
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
}
// 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.lazyInit()
return m.ListKVMap.Pops(size)
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
}
// 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 {
m.lazyInit()
return m.ListKVMap.GetOrSet(key, value)
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 *ListMap) GetOrSetFunc(key any, f func() any) any {
m.lazyInit()
return m.ListKVMap.GetOrSetFunc(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
@ -169,50 +333,55 @@ 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 {
m.lazyInit()
return m.ListKVMap.GetOrSetFuncLock(key, f)
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// 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 {
m.lazyInit()
return m.ListKVMap.GetVar(key)
return gvar.New(m.Get(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 {
m.lazyInit()
return m.ListKVMap.GetVarOrSet(key, value)
return gvar.New(m.GetOrSet(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 {
m.lazyInit()
return m.ListKVMap.GetVarOrSetFunc(key, f)
return gvar.New(m.GetOrSetFunc(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 {
m.lazyInit()
return m.ListKVMap.GetVarOrSetFuncLock(key, f)
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 *ListMap) SetIfNotExist(key any, value any) bool {
m.lazyInit()
return m.ListKVMap.SetIfNotExist(key, value)
if !m.Contains(key) {
m.doSetWithLockCheck(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 *ListMap) SetIfNotExistFunc(key any, f func() any) bool {
m.lazyInit()
return m.ListKVMap.SetIfNotExistFunc(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
@ -221,52 +390,100 @@ 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 {
m.lazyInit()
return m.ListKVMap.SetIfNotExistFuncLock(key, f)
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// Remove deletes value from map by given `key`, and return this deleted value.
func (m *ListMap) Remove(key any) (value any) {
m.lazyInit()
return m.ListKVMap.Remove(key)
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
}
// Removes batch deletes values of the map by keys.
func (m *ListMap) Removes(keys []any) {
m.lazyInit()
m.ListKVMap.Removes(keys)
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 *ListMap) Keys() []any {
m.lazyInit()
return m.ListKVMap.Keys()
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
}
// Values returns all values of the map as a slice.
func (m *ListMap) Values() []any {
m.lazyInit()
return m.ListKVMap.Values()
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
}
// 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.lazyInit()
return m.ListKVMap.Contains(key)
m.mu.RLock()
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return
}
// Size returns the size of the map.
func (m *ListMap) Size() (size int) {
m.lazyInit()
return m.ListKVMap.Size()
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 *ListMap) IsEmpty() bool {
m.lazyInit()
return m.ListKVMap.IsEmpty()
return m.Size() == 0
}
// Flip exchanges key-value of the map to value-key.
@ -281,35 +498,90 @@ 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.lazyInit()
other.lazyInit()
m.ListKVMap.Merge(other.ListKVMap)
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
})
}
// String returns the map as a string.
func (m *ListMap) String() string {
m.lazyInit()
return m.ListKVMap.String()
if m == nil {
return ""
}
b, _ := m.MarshalJSON()
return string(b)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m ListMap) MarshalJSON() (jsonBytes []byte, err error) {
return m.ListKVMap.MarshalJSON()
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
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *ListMap) UnmarshalJSON(b []byte) error {
m.lazyInit()
return m.ListKVMap.UnmarshalJSON(b)
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
}
// 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})
@ -325,8 +597,16 @@ func (m *ListMap) DeepCopy() any {
if m == nil {
return nil
}
m.lazyInit()
return &ListMap{
ListKVMap: m.ListKVMap.DeepCopy().(*ListKVMap[any, any]),
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
})
}
return NewListMapFrom(data, m.mu.IsSafe())
}

View File

@ -1,32 +0,0 @@
// 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,49 +443,3 @@ 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,42 +96,6 @@ 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

@ -1,326 +0,0 @@
// 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,19 +8,41 @@
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 {
*TPool[any]
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.
}
// NewFunc Creation function for object.
type NewFunc = TPoolNewFunc[any]
type NewFunc func() (any, error)
// ExpireFunc Destruction function for object.
type ExpireFunc = TPoolExpireFunc[any]
type ExpireFunc func(any)
// New creates and returns a new object pool.
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
@ -30,40 +52,134 @@ type ExpireFunc = TPoolExpireFunc[any]
// ttl < 0 : immediate expired after use;
// ttl > 0 : timeout expired;
func New(ttl time.Duration, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
return &Pool{
TPool: NewTPool(ttl, newFunc, expireFunc...),
r := &Pool{
list: glist.New(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 *Pool) Put(value any) error {
return p.TPool.Put(value)
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
}
// MustPut puts an item to pool, it panics if any error occurs.
func (p *Pool) MustPut(value any) {
p.TPool.MustPut(value)
if err := p.Put(value); err != nil {
panic(err)
}
}
// Clear clears pool, which means it will remove all items from pool.
func (p *Pool) Clear() {
p.TPool.Clear()
if p.ExpireFunc != nil {
for {
if r := p.list.PopFront(); r != nil {
p.ExpireFunc(r.(*poolItem).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 *Pool) Get() (any, error) {
return p.TPool.Get()
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")
}
// Size returns the count of available items of pool.
func (p *Pool) Size() int {
return p.TPool.Size()
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 *Pool) Close() {
p.TPool.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
}
}
}

View File

@ -1,183 +0,0 @@
// 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

@ -1,112 +0,0 @@
// 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 := range 10 {
for index := 0; index < 10; index++ {
p2.Put(index)
}
t.Assert(p2.Size(), 10)

View File

@ -17,9 +17,20 @@
// 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 {
*TQueue[any]
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.
}
const (
@ -31,35 +42,74 @@ 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 {
return &Queue{
TQueue: NewTQueue[any](limit...),
q := &Queue{
closed: gtype.NewBool(),
}
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) {
q.TQueue.Push(v)
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 *Queue) Pop() any {
return q.TQueue.Pop()
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 *Queue) Close() {
q.TQueue.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 *Queue) Len() (length int64) {
return q.TQueue.Len()
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.
@ -68,3 +118,34 @@ 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

@ -1,134 +0,0 @@
// 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,218 +128,3 @@ 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,11 +9,27 @@
// 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 {
*TRing[any]
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
}
// New creates and returns a Ring structure of `cap` elements.
@ -23,53 +39,108 @@ type Ring struct {
// Deprecated.
func New(cap int, safe ...bool) *Ring {
return &Ring{
TRing: NewTRing[any](cap, safe...),
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 *Ring) Val() any {
return r.TRing.Val()
var value any
r.mu.RLock()
if r.ring.Value != nil {
value = r.ring.Value.(internalRingItem).Value
}
r.mu.RUnlock()
return value
}
// Len returns the size of ring.
func (r *Ring) Len() int {
return r.TRing.Len()
r.checkAndUpdateLenAndCap()
return r.len.Val()
}
// Cap returns the capacity of ring.
func (r *Ring) Cap() int {
return r.TRing.Cap()
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)
}
// Set sets value to the item of current position.
func (r *Ring) Set(value any) *Ring {
r.TRing.Set(value)
r.mu.Lock()
if r.ring.Value == nil {
r.len.Add(1)
}
r.ring.Value = internalRingItem{Value: value}
r.mu.Unlock()
return r
}
// Put sets `value` to current item of ring and moves position to next item.
func (r *Ring) Put(value any) *Ring {
r.TRing.Put(value)
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()
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.TRing.Move(n)
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 *Ring) Prev() *Ring {
r.TRing.Prev()
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 *Ring) Next() *Ring {
r.TRing.Next()
r.mu.Lock()
r.ring = r.ring.Next()
r.mu.Unlock()
return r
}
@ -89,7 +160,13 @@ 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.TRing.Link(s.TRing)
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
}
@ -97,31 +174,78 @@ 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 {
return &Ring{
TRing: r.TRing.Unlink(n),
}
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
}
// 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.TRing.RLockIteratorNext(f)
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
}
}
}
// RLockIteratorPrev iterates and locks reading backward
// RLockIteratorPrev iterates and locks writing 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.TRing.RLockIteratorPrev(f)
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
}
}
}
// SliceNext returns a copy of all item values as slice forward from current position.
func (r *Ring) SliceNext() []any {
return r.TRing.SliceNext()
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
}
// SlicePrev returns a copy of all item values as slice backward from current position.
func (r *Ring) SlicePrev() []any {
return r.TRing.SlicePrev()
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
}

View File

@ -1,244 +0,0 @@
// 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,11 +148,14 @@ 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)
@ -160,11 +163,14 @@ 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,15 +8,18 @@
package gset
import (
"sync"
"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"
)
// Set is consisted of any items.
type Set struct {
*TSet[any]
once sync.Once
mu rwmutex.RWMutex
data map[any]struct{}
}
// New create and returns a new set, which contains un-repeated items.
@ -30,38 +33,44 @@ func New(safe ...bool) *Set {
// Also see New.
func NewSet(safe ...bool) *Set {
return &Set{
TSet: NewTSet[any](safe...),
data: make(map[any]struct{}),
mu: rwmutex.Create(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 {
return &Set{
TSet: NewTSetFrom[any](gconv.Interfaces(items), safe...),
m := make(map[any]struct{})
for _, v := range gconv.Interfaces(items) {
m[v] = struct{}{}
}
return &Set{
data: m,
mu: rwmutex.Create(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) {
set.lazyInit()
set.TSet.Iterator(f)
for _, k := range set.Slice() {
if !f(k) {
break
}
}
}
// Add adds one or multiple items to the set.
func (set *Set) Add(items ...any) {
set.lazyInit()
set.TSet.Add(items...)
set.mu.Lock()
if set.data == nil {
set.data = make(map[any]struct{})
}
for _, v := range items {
set.data[v] = struct{}{}
}
set.mu.Unlock()
}
// AddIfNotExist checks whether item exists in the set,
@ -70,8 +79,21 @@ 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 {
set.lazyInit()
return set.TSet.AddIfNotExist(item)
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
}
// AddIfNotExistFunc checks whether item exists in the set,
@ -81,8 +103,23 @@ 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 {
set.lazyInit()
return set.TSet.AddIfNotExistFunc(item, f)
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
}
// AddIfNotExistFuncLock checks whether item exists in the set,
@ -92,44 +129,95 @@ 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 {
set.lazyInit()
return set.TSet.AddIfNotExistFuncLock(item, f)
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
}
// Contains checks whether the set contains `item`.
func (set *Set) Contains(item any) bool {
set.lazyInit()
return set.TSet.Contains(item)
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 *Set) Remove(item any) {
set.lazyInit()
set.TSet.Remove(item)
set.mu.Lock()
if set.data != nil {
delete(set.data, item)
}
set.mu.Unlock()
}
// Size returns the size of the set.
func (set *Set) Size() int {
set.lazyInit()
return set.TSet.Size()
set.mu.RLock()
l := len(set.data)
set.mu.RUnlock()
return l
}
// Clear deletes all items of the set.
func (set *Set) Clear() {
set.lazyInit()
set.TSet.Clear()
set.mu.Lock()
set.data = make(map[any]struct{})
set.mu.Unlock()
}
// Slice returns all items of the set as slice.
func (set *Set) Slice() []any {
set.lazyInit()
return set.TSet.Slice()
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
}
// Join joins items with a string `glue`.
func (set *Set) Join(glue string) string {
set.lazyInit()
return set.TSet.Join(glue)
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.
@ -137,27 +225,63 @@ func (set *Set) String() string {
if set == nil {
return ""
}
set.lazyInit()
return set.TSet.String()
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 *Set) LockFunc(f func(m map[any]struct{})) {
set.lazyInit()
set.TSet.LockFunc(f)
set.mu.Lock()
defer set.mu.Unlock()
f(set.data)
}
// RLockFunc locks reading with callback function `f`.
func (set *Set) RLockFunc(f func(m map[any]struct{})) {
set.lazyInit()
set.TSet.RLockFunc(f)
set.mu.RLock()
defer set.mu.RUnlock()
f(set.data)
}
// Equal checks whether the two sets equal.
func (set *Set) Equal(other *Set) bool {
set.lazyInit()
other.lazyInit()
return set.TSet.Equal(other.TSet)
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`.
@ -165,40 +289,85 @@ func (set *Set) IsSubsetOf(other *Set) bool {
if set == other {
return true
}
set.lazyInit()
other.lazyInit()
return set.TSet.IsSubsetOf(other.TSet)
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 *Set) Union(others ...*Set) (newSet *Set) {
set.lazyInit()
return &Set{
TSet: set.TSet.Union(set.toTSetSlice(others)...),
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()
}
}
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 *Set) Diff(others ...*Set) (newSet *Set) {
set.lazyInit()
return &Set{
TSet: set.TSet.Diff(set.toTSetSlice(others)...),
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()
}
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) {
set.lazyInit()
return &Set{
TSet: set.TSet.Intersect(set.toTSetSlice(others)...),
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()
}
}
return
}
// Complement returns a new set which is the complement from `set` to `full`.
@ -207,22 +376,36 @@ 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) {
set.lazyInit()
if full == nil {
return &Set{
TSet: NewTSet[any](true),
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
}
}
full.lazyInit()
return &Set{
TSet: set.TSet.Complement(full.TSet),
}
return
}
// Merge adds items from `others` sets into `set`.
func (set *Set) Merge(others ...*Set) *Set {
set.lazyInit()
set.TSet.Merge(set.toTSetSlice(others)...)
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()
}
}
return set
}
@ -230,46 +413,101 @@ 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.lazyInit()
return set.TSet.Sum()
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 *Set) Pop() any {
set.lazyInit()
return set.TSet.Pop()
set.mu.Lock()
defer set.mu.Unlock()
for k := range set.data {
delete(set.data, k)
return k
}
return nil
}
// Pops randomly pops `size` items from set.
// It returns all items if size == -1.
func (set *Set) Pops(size int) []any {
set.lazyInit()
return set.TSet.Pops(size)
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
}
// Walk applies a user supplied function `f` to every item of set.
func (set *Set) Walk(f func(item any) any) *Set {
set.lazyInit()
set.TSet.Walk(f)
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
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set Set) MarshalJSON() ([]byte, error) {
set.lazyInit()
return set.TSet.MarshalJSON()
return json.Marshal(set.Slice())
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (set *Set) UnmarshalJSON(b []byte) error {
set.lazyInit()
return set.TSet.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for set.
func (set *Set) UnmarshalValue(value any) (err error) {
set.lazyInit()
return set.TSet.UnmarshalValue(value)
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
}
// DeepCopy implements interface for deep copy of current type.
@ -277,21 +515,11 @@ func (set *Set) DeepCopy() any {
if set == nil {
return nil
}
set.lazyInit()
return &Set{
TSet: set.TSet.DeepCopy().(*TSet[any]),
set.mu.RLock()
defer set.mu.RUnlock()
data := make([]any, 0)
for k := range set.data {
data = append(data, k)
}
}
// 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
return NewFrom(data, set.mu.IsSafe())
}

View File

@ -8,13 +8,17 @@
package gset
import (
"sync"
"bytes"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/rwmutex"
"github.com/gogf/gf/v2/util/gconv"
)
// IntSet is consisted of int items.
type IntSet struct {
*TSet[int]
once sync.Once
mu rwmutex.RWMutex
data map[int]struct{}
}
// NewIntSet create and returns a new set, which contains un-repeated items.
@ -22,37 +26,43 @@ type IntSet struct {
// which is false in default.
func NewIntSet(safe ...bool) *IntSet {
return &IntSet{
TSet: NewTSet[int](safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]struct{}),
}
}
// NewIntSetFrom returns a new set from `items`.
func NewIntSetFrom(items []int, safe ...bool) *IntSet {
return &IntSet{
TSet: NewTSetFrom(items, safe...),
m := make(map[int]struct{})
for _, v := range items {
m[v] = struct{}{}
}
return &IntSet{
mu: rwmutex.Create(safe...),
data: m,
}
}
// 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) {
set.lazyInit()
set.TSet.Iterator(f)
for _, k := range set.Slice() {
if !f(k) {
break
}
}
}
// Add adds one or multiple items to the set.
func (set *IntSet) Add(item ...int) {
set.lazyInit()
set.TSet.Add(item...)
set.mu.Lock()
if set.data == nil {
set.data = make(map[int]struct{})
}
for _, v := range item {
set.data[v] = struct{}{}
}
set.mu.Unlock()
}
// AddIfNotExist checks whether item exists in the set,
@ -61,8 +71,18 @@ 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 {
set.lazyInit()
return set.TSet.AddIfNotExist(item)
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
}
// AddIfNotExistFunc checks whether item exists in the set,
@ -71,8 +91,20 @@ 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 {
set.lazyInit()
return set.TSet.AddIfNotExistFunc(item, f)
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
}
// AddIfNotExistFuncLock checks whether item exists in the set,
@ -81,44 +113,92 @@ 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 {
set.lazyInit()
return set.TSet.AddIfNotExistFuncLock(item, f)
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
}
// Contains checks whether the set contains `item`.
func (set *IntSet) Contains(item int) bool {
set.lazyInit()
return set.TSet.Contains(item)
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 *IntSet) Remove(item int) {
set.lazyInit()
set.TSet.Remove(item)
set.mu.Lock()
if set.data != nil {
delete(set.data, item)
}
set.mu.Unlock()
}
// Size returns the size of the set.
func (set *IntSet) Size() int {
set.lazyInit()
return set.TSet.Size()
set.mu.RLock()
l := len(set.data)
set.mu.RUnlock()
return l
}
// Clear deletes all items of the set.
func (set *IntSet) Clear() {
set.lazyInit()
set.TSet.Clear()
set.mu.Lock()
set.data = make(map[int]struct{})
set.mu.Unlock()
}
// Slice returns the an of items of the set as slice.
func (set *IntSet) Slice() []int {
set.lazyInit()
return set.TSet.Slice()
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
}
// Join joins items with a string `glue`.
func (set *IntSet) Join(glue string) string {
set.lazyInit()
return set.TSet.Join(glue)
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.
@ -126,27 +206,41 @@ func (set *IntSet) String() string {
if set == nil {
return ""
}
set.lazyInit()
return set.TSet.String()
return "[" + set.Join(",") + "]"
}
// LockFunc locks writing with callback function `f`.
func (set *IntSet) LockFunc(f func(m map[int]struct{})) {
set.lazyInit()
set.TSet.LockFunc(f)
set.mu.Lock()
defer set.mu.Unlock()
f(set.data)
}
// RLockFunc locks reading with callback function `f`.
func (set *IntSet) RLockFunc(f func(m map[int]struct{})) {
set.lazyInit()
set.TSet.RLockFunc(f)
set.mu.RLock()
defer set.mu.RUnlock()
f(set.data)
}
// Equal checks whether the two sets equal.
func (set *IntSet) Equal(other *IntSet) bool {
set.lazyInit()
other.lazyInit()
return set.TSet.Equal(other.TSet)
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`.
@ -154,38 +248,85 @@ func (set *IntSet) IsSubsetOf(other *IntSet) bool {
if set == other {
return true
}
set.lazyInit()
other.lazyInit()
return set.TSet.IsSubsetOf(other.TSet)
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 `other`.
// Which means, all the items in `newSet` are in `set` or in `other`.
func (set *IntSet) Union(others ...*IntSet) (newSet *IntSet) {
set.lazyInit()
return &IntSet{
TSet: set.TSet.Union(set.toTSetSlice(others)...),
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()
}
}
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) {
set.lazyInit()
return &IntSet{
TSet: set.TSet.Diff(set.toTSetSlice(others)...),
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()
}
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) {
set.lazyInit()
return &IntSet{
TSet: set.TSet.Intersect(set.toTSetSlice(others)...),
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()
}
}
return
}
// Complement returns a new set which is the complement from `set` to `full`.
@ -194,22 +335,36 @@ 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) {
set.lazyInit()
if full == nil {
return &IntSet{
TSet: NewTSet[int](),
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
}
}
full.lazyInit()
return &IntSet{
TSet: set.TSet.Complement(full.TSet),
}
return
}
// Merge adds items from `others` sets into `set`.
func (set *IntSet) Merge(others ...*IntSet) *IntSet {
set.lazyInit()
set.TSet.Merge(set.toTSetSlice(others)...)
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()
}
}
return set
}
@ -217,46 +372,101 @@ 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.lazyInit()
return set.TSet.Sum()
set.mu.RLock()
defer set.mu.RUnlock()
for k := range set.data {
sum += k
}
return
}
// Pop randomly pops an item from set.
func (set *IntSet) Pop() int {
set.lazyInit()
return set.TSet.Pop()
set.mu.Lock()
defer set.mu.Unlock()
for k := range set.data {
delete(set.data, k)
return k
}
return 0
}
// Pops randomly pops `size` items from set.
// It returns all items if size == -1.
func (set *IntSet) Pops(size int) []int {
set.lazyInit()
return set.TSet.Pops(size)
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
}
// Walk applies a user supplied function `f` to every item of set.
func (set *IntSet) Walk(f func(item int) int) *IntSet {
set.lazyInit()
set.TSet.Walk(f)
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
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set IntSet) MarshalJSON() ([]byte, error) {
set.lazyInit()
return set.TSet.MarshalJSON()
return json.Marshal(set.Slice())
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (set *IntSet) UnmarshalJSON(b []byte) error {
set.lazyInit()
return set.TSet.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for set.
func (set *IntSet) UnmarshalValue(value any) (err error) {
set.lazyInit()
return set.TSet.UnmarshalValue(value)
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
}
// DeepCopy implements interface for deep copy of current type.
@ -264,21 +474,15 @@ func (set *IntSet) DeepCopy() any {
if set == nil {
return nil
}
set.lazyInit()
return &IntSet{
TSet: set.TSet.DeepCopy().(*TSet[int]),
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++
}
}
// 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
return NewIntSetFrom(slice, set.mu.IsSafe())
}

View File

@ -8,14 +8,19 @@
package gset
import (
"bytes"
"strings"
"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"
)
// StrSet is consisted of string items.
type StrSet struct {
*TSet[string]
once sync.Once
mu rwmutex.RWMutex
data map[string]struct{}
}
// NewStrSet create and returns a new set, which contains un-repeated items.
@ -23,45 +28,61 @@ type StrSet struct {
// which is false in default.
func NewStrSet(safe ...bool) *StrSet {
return &StrSet{
TSet: NewTSet[string](safe...),
mu: rwmutex.Create(safe...),
data: make(map[string]struct{}),
}
}
// NewStrSetFrom returns a new set from `items`.
func NewStrSetFrom(items []string, safe ...bool) *StrSet {
return &StrSet{
TSet: NewTSetFrom(items, safe...),
m := make(map[string]struct{})
for _, v := range items {
m[v] = struct{}{}
}
return &StrSet{
mu: rwmutex.Create(safe...),
data: m,
}
}
// 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) {
set.lazyInit()
set.TSet.Iterator(f)
for _, k := range set.Slice() {
if !f(k) {
break
}
}
}
// Add adds one or multiple items to the set.
func (set *StrSet) Add(item ...string) {
set.lazyInit()
set.TSet.Add(item...)
set.mu.Lock()
if set.data == nil {
set.data = make(map[string]struct{})
}
for _, v := range item {
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 exist in the set,
// or else it does nothing and returns false.
func (set *StrSet) AddIfNotExist(item string) bool {
set.lazyInit()
return set.TSet.AddIfNotExist(item)
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
}
// AddIfNotExistFunc checks whether item exists in the set,
@ -70,8 +91,20 @@ 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 {
set.lazyInit()
return set.TSet.AddIfNotExistFunc(item, f)
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
}
// AddIfNotExistFuncLock checks whether item exists in the set,
@ -80,20 +113,36 @@ 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 {
set.lazyInit()
return set.TSet.AddIfNotExistFuncLock(item, f)
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
}
// Contains checks whether the set contains `item`.
func (set *StrSet) Contains(item string) bool {
set.lazyInit()
return set.TSet.Contains(item)
var ok bool
set.mu.RLock()
if set.data != nil {
_, ok = set.data[item]
}
set.mu.RUnlock()
return ok
}
// 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 {
@ -106,32 +155,64 @@ func (set *StrSet) ContainsI(item string) bool {
// Remove deletes `item` from set.
func (set *StrSet) Remove(item string) {
set.lazyInit()
set.TSet.Remove(item)
set.mu.Lock()
if set.data != nil {
delete(set.data, item)
}
set.mu.Unlock()
}
// Size returns the size of the set.
func (set *StrSet) Size() int {
set.lazyInit()
return set.TSet.Size()
set.mu.RLock()
l := len(set.data)
set.mu.RUnlock()
return l
}
// Clear deletes all items of the set.
func (set *StrSet) Clear() {
set.lazyInit()
set.TSet.Clear()
set.mu.Lock()
set.data = make(map[string]struct{})
set.mu.Unlock()
}
// Slice returns the an of items of the set as slice.
func (set *StrSet) Slice() []string {
set.lazyInit()
return set.TSet.Slice()
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
}
// Join joins items with a string `glue`.
func (set *StrSet) Join(glue string) string {
set.lazyInit()
return set.TSet.Join(glue)
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()
}
// String returns items as a string, which implements like json.Marshal does.
@ -139,27 +220,57 @@ func (set *StrSet) String() string {
if set == nil {
return ""
}
set.lazyInit()
return set.TSet.String()
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()
}
// LockFunc locks writing with callback function `f`.
func (set *StrSet) LockFunc(f func(m map[string]struct{})) {
set.lazyInit()
set.TSet.LockFunc(f)
set.mu.Lock()
defer set.mu.Unlock()
f(set.data)
}
// RLockFunc locks reading with callback function `f`.
func (set *StrSet) RLockFunc(f func(m map[string]struct{})) {
set.lazyInit()
set.TSet.RLockFunc(f)
set.mu.RLock()
defer set.mu.RUnlock()
f(set.data)
}
// Equal checks whether the two sets equal.
func (set *StrSet) Equal(other *StrSet) bool {
set.lazyInit()
other.lazyInit()
return set.TSet.Equal(other.TSet)
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`.
@ -167,38 +278,85 @@ func (set *StrSet) IsSubsetOf(other *StrSet) bool {
if set == other {
return true
}
set.lazyInit()
other.lazyInit()
return set.TSet.IsSubsetOf(other.TSet)
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 `other`.
// Which means, all the items in `newSet` are in `set` or in `other`.
func (set *StrSet) Union(others ...*StrSet) (newSet *StrSet) {
set.lazyInit()
return &StrSet{
TSet: set.TSet.Union(set.toTSetSlice(others)...),
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()
}
}
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) {
set.lazyInit()
return &StrSet{
TSet: set.TSet.Diff(set.toTSetSlice(others)...),
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()
}
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) {
set.lazyInit()
return &StrSet{
TSet: set.TSet.Intersect(set.toTSetSlice(others)...),
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()
}
}
return
}
// Complement returns a new set which is the complement from `set` to `full`.
@ -207,22 +365,36 @@ 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) {
set.lazyInit()
if full == nil {
return &StrSet{
TSet: NewTSet[string](),
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
}
}
full.lazyInit()
return &StrSet{
TSet: set.TSet.Complement(full.TSet),
}
return
}
// Merge adds items from `others` sets into `set`.
func (set *StrSet) Merge(others ...*StrSet) *StrSet {
set.lazyInit()
set.TSet.Merge(set.toTSetSlice(others)...)
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()
}
}
return set
}
@ -230,46 +402,101 @@ 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.lazyInit()
return set.TSet.Sum()
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 *StrSet) Pop() string {
set.lazyInit()
return set.TSet.Pop()
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 *StrSet) Pops(size int) []string {
set.lazyInit()
return set.TSet.Pops(size)
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
}
// Walk applies a user supplied function `f` to every item of set.
func (set *StrSet) Walk(f func(item string) string) *StrSet {
set.lazyInit()
set.TSet.Walk(f)
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
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set StrSet) MarshalJSON() ([]byte, error) {
set.lazyInit()
return set.TSet.MarshalJSON()
return json.Marshal(set.Slice())
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (set *StrSet) UnmarshalJSON(b []byte) error {
set.lazyInit()
return set.TSet.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for set.
func (set *StrSet) UnmarshalValue(value any) (err error) {
set.lazyInit()
return set.TSet.UnmarshalValue(value)
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
}
// DeepCopy implements interface for deep copy of current type.
@ -277,21 +504,15 @@ func (set *StrSet) DeepCopy() any {
if set == nil {
return nil
}
set.lazyInit()
return &StrSet{
TSet: set.TSet.DeepCopy().(*TSet[string]),
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++
}
}
// 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
return NewStrSetFrom(slice, set.mu.IsSafe())
}

View File

@ -1,531 +0,0 @@
// 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,19 +187,6 @@ 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) {
@ -249,14 +236,6 @@ 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,19 +167,6 @@ 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) {
@ -229,14 +216,6 @@ 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,19 +178,6 @@ 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) {
@ -240,14 +227,6 @@ 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

@ -1,593 +0,0 @@
// 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)
}
// iteratorFromGetIndexT returns the index of the key in the keys slice.
// 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 iteratorFromGetIndexT[T comparable](key T, keys []T, match bool) (index int, canIterator bool) {
func iteratorFromGetIndex(key any, keys []any, match bool) (index int, canIterator bool) {
if match {
for i, k := range keys {
if k == key {
@ -176,19 +176,10 @@ func iteratorFromGetIndexT[T comparable](key T, keys []T, match bool) (index int
}
}
} else {
if i, ok := any(key).(int); ok {
if i, ok := 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,22 +7,31 @@
package gtree
import (
"sync"
"fmt"
"github.com/emirpasic/gods/trees/avltree"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/internal/rwmutex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
var _ iTree = (*AVLTree)(nil)
// AVLTree holds elements of the AVL tree.
type AVLTree struct {
*AVLKVTree[any, any]
once sync.Once
mu rwmutex.RWMutex
root *AVLTreeNode
comparator func(v1, v2 any) int
tree *avltree.Tree
}
// AVLTreeNode is a single element within the tree.
type AVLTreeNode = AVLKVTreeNode[any, any]
type AVLTreeNode struct {
Key any
Value any
}
// NewAVLTree instantiates an AVL tree with the custom key comparator.
//
@ -30,7 +39,9 @@ type AVLTreeNode = AVLKVTreeNode[any, any]
// which is false in default.
func NewAVLTree(comparator func(v1, v2 any) int, safe ...bool) *AVLTree {
return &AVLTree{
AVLKVTree: NewAVLKVTree[any, any](comparator, safe...),
mu: rwmutex.Create(safe...),
comparator: comparator,
tree: avltree.NewWith(comparator),
}
}
@ -38,55 +49,58 @@ 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 {
return &AVLTree{
AVLKVTree: NewAVLKVTreeFrom(comparator, data, safe...),
tree := NewAVLTree(comparator, safe...)
for k, v := range data {
tree.doSet(k, v)
}
}
// 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)
}
})
return tree
}
// Clone clones and returns a new tree from current tree.
func (tree *AVLTree) Clone() *AVLTree {
if tree == nil {
return nil
}
tree.lazyInit()
return &AVLTree{
AVLKVTree: tree.AVLKVTree.Clone(),
}
newTree := NewAVLTree(tree.comparator, tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
// Set sets key-value pair into the tree.
func (tree *AVLTree) Set(key any, value any) {
tree.lazyInit()
tree.AVLKVTree.Set(key, value)
tree.mu.Lock()
defer tree.mu.Unlock()
tree.doSet(key, value)
}
// Sets batch sets key-values to the tree.
func (tree *AVLTree) Sets(data map[any]any) {
tree.lazyInit()
tree.AVLKVTree.Sets(data)
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 *AVLTree) SetIfNotExist(key any, value any) bool {
tree.lazyInit()
return tree.AVLKVTree.SetIfNotExist(key, value)
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 *AVLTree) SetIfNotExistFunc(key any, f func() any) bool {
tree.lazyInit()
return tree.AVLKVTree.SetIfNotExistFunc(key, f)
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.
@ -95,8 +109,13 @@ 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.lazyInit()
return tree.AVLKVTree.SetIfNotExistFuncLock(key, f)
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.
@ -104,22 +123,32 @@ 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) {
tree.lazyInit()
return tree.AVLKVTree.Get(key)
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 *AVLTree) GetOrSet(key any, value any) any {
tree.lazyInit()
return tree.AVLKVTree.GetOrSet(key, value)
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 *AVLTree) GetOrSetFunc(key any, f func() any) any {
tree.lazyInit()
return tree.AVLKVTree.GetOrSetFunc(key, f)
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
@ -127,8 +156,13 @@ 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.lazyInit()
return tree.AVLKVTree.GetOrSetFuncLock(key, f)
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`.
@ -136,8 +170,7 @@ func (tree *AVLTree) GetOrSetFuncLock(key any, f func() any) any {
//
// Also see function Get.
func (tree *AVLTree) GetVar(key any) *gvar.Var {
tree.lazyInit()
return tree.AVLKVTree.GetVar(key)
return gvar.New(tree.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
@ -145,8 +178,7 @@ func (tree *AVLTree) GetVar(key any) *gvar.Var {
//
// Also see function GetOrSet.
func (tree *AVLTree) GetVarOrSet(key any, value any) *gvar.Var {
tree.lazyInit()
return tree.AVLKVTree.GetVarOrSet(key, value)
return gvar.New(tree.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
@ -154,8 +186,7 @@ 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 {
tree.lazyInit()
return tree.AVLKVTree.GetVarOrSetFunc(key, f)
return gvar.New(tree.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
@ -163,100 +194,127 @@ 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 {
tree.lazyInit()
return tree.AVLKVTree.GetVarOrSetFuncLock(key, f)
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 *AVLTree) Search(key any) (value any, found bool) {
tree.lazyInit()
return tree.AVLKVTree.Search(key)
tree.mu.RLock()
defer tree.mu.RUnlock()
if node, found := tree.doGet(key); found {
return node, true
}
return nil, false
}
// Contains checks and returns whether given `key` exists in the tree.
func (tree *AVLTree) Contains(key any) bool {
tree.lazyInit()
return tree.AVLKVTree.Contains(key)
tree.mu.RLock()
defer tree.mu.RUnlock()
_, ok := tree.doGet(key)
return ok
}
// Size returns number of nodes in the tree.
func (tree *AVLTree) Size() int {
tree.lazyInit()
return tree.AVLKVTree.Size()
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.tree.Size()
}
// IsEmpty returns true if the tree does not contain any nodes.
func (tree *AVLTree) IsEmpty() bool {
tree.lazyInit()
return tree.AVLKVTree.IsEmpty()
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 *AVLTree) Remove(key any) (value any) {
tree.lazyInit()
return tree.AVLKVTree.Remove(key)
tree.mu.Lock()
defer tree.mu.Unlock()
return tree.doRemove(key)
}
// Removes batch deletes key-value pairs from the tree by `keys`.
func (tree *AVLTree) Removes(keys []any) {
tree.lazyInit()
tree.AVLKVTree.Removes(keys)
tree.mu.Lock()
defer tree.mu.Unlock()
for _, key := range keys {
tree.doRemove(key)
}
}
// Clear removes all nodes from the tree.
func (tree *AVLTree) Clear() {
tree.lazyInit()
tree.AVLKVTree.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 *AVLTree) Keys() []any {
tree.lazyInit()
return tree.AVLKVTree.Keys()
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 *AVLTree) Values() []any {
tree.lazyInit()
return tree.AVLKVTree.Values()
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 *AVLTree) Replace(data map[any]any) {
tree.lazyInit()
tree.AVLKVTree.Replace(data)
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 *AVLTree) Print() {
tree.lazyInit()
tree.AVLKVTree.Print()
fmt.Println(tree.String())
}
// String returns a string representation of container.
func (tree *AVLTree) String() string {
tree.lazyInit()
return tree.AVLKVTree.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 *AVLTree) MarshalJSON() (jsonBytes []byte, err error) {
tree.lazyInit()
return tree.AVLKVTree.MarshalJSON()
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.tree.MarshalJSON()
}
// Map returns all key-value pairs as map.
func (tree *AVLTree) Map() map[any]any {
tree.lazyInit()
return tree.AVLKVTree.Map()
m := make(map[any]any, tree.Size())
tree.IteratorAsc(func(key, value any) bool {
m[key] = value
return true
})
return m
}
// MapStrAny returns all key-value items as map[string]any.
func (tree *AVLTree) MapStrAny() map[string]any {
tree.lazyInit()
return tree.AVLKVTree.MapStrAny()
m := make(map[string]any, tree.Size())
tree.IteratorAsc(func(key, value any) bool {
m[gconv.String(key)] = value
return true
})
return m
}
// Iterator is alias of IteratorAsc.
@ -276,8 +334,18 @@ 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.lazyInit()
tree.AVLKVTree.IteratorAsc(f)
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`.
@ -287,16 +355,34 @@ 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.lazyInit()
tree.AVLKVTree.IteratorAscFrom(key, match, f)
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]))
}
}
// 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.lazyInit()
tree.AVLKVTree.IteratorDesc(f)
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`.
@ -306,20 +392,44 @@ 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.lazyInit()
tree.AVLKVTree.IteratorDescFrom(key, match, f)
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]))
}
}
// 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.lazyInit()
return tree.AVLKVTree.Left()
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.tree.Left()
if node == nil {
return nil
}
return &AVLTreeNode{
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 *AVLTree) Right() *AVLTreeNode {
tree.lazyInit()
return tree.AVLKVTree.Right()
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.tree.Right()
if node == nil {
return nil
}
return &AVLTreeNode{
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.
@ -331,8 +441,16 @@ 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.lazyInit()
return tree.AVLKVTree.Floor(key)
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
}
// Ceiling finds ceiling node of the input key, returns the ceiling node or nil if no ceiling node is found.
@ -344,8 +462,16 @@ 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.lazyInit()
return tree.AVLKVTree.Ceiling(key)
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
}
// Flip exchanges key-value of the tree to value-key.
@ -354,8 +480,6 @@ 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())
@ -369,3 +493,32 @@ 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,22 +7,31 @@
package gtree
import (
"sync"
"fmt"
"github.com/emirpasic/gods/trees/btree"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/internal/rwmutex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
var _ iTree = (*BTree)(nil)
// BTree holds elements of the B-tree.
type BTree struct {
*BKVTree[any, any]
once sync.Once
mu rwmutex.RWMutex
comparator func(v1, v2 any) int
m int // order (maximum number of children)
tree *btree.Tree
}
// BTreeEntry represents the key-value pair contained within nodes.
type BTreeEntry = BKVTreeEntry[any, any]
type BTreeEntry struct {
Key any
Value 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,
@ -30,7 +39,10 @@ type BTreeEntry = BKVTreeEntry[any, any]
// 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{
BKVTree: NewBKVTree[any, any](m, comparator, safe...),
mu: rwmutex.Create(safe...),
m: m,
comparator: comparator,
tree: btree.NewWith(m, comparator),
}
}
@ -38,55 +50,58 @@ 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 {
return &BTree{
BKVTree: NewBKVTreeFrom(m, comparator, data, safe...),
tree := NewBTree(m, comparator, safe...)
for k, v := range data {
tree.doSet(k, v)
}
}
// 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)
}
})
return tree
}
// Clone clones and returns a new tree from current tree.
func (tree *BTree) Clone() *BTree {
if tree == nil {
return nil
}
tree.lazyInit()
return &BTree{
BKVTree: tree.BKVTree.Clone(),
}
newTree := NewBTree(tree.m, tree.comparator, tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
// Set sets key-value pair into the tree.
func (tree *BTree) Set(key any, value any) {
tree.lazyInit()
tree.BKVTree.Set(key, value)
tree.mu.Lock()
defer tree.mu.Unlock()
tree.doSet(key, value)
}
// Sets batch sets key-values to the tree.
func (tree *BTree) Sets(data map[any]any) {
tree.lazyInit()
tree.BKVTree.Sets(data)
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 *BTree) SetIfNotExist(key any, value any) bool {
tree.lazyInit()
return tree.BKVTree.SetIfNotExist(key, value)
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 *BTree) SetIfNotExistFunc(key any, f func() any) bool {
tree.lazyInit()
return tree.BKVTree.SetIfNotExistFunc(key, f)
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.
@ -95,8 +110,13 @@ 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.lazyInit()
return tree.BKVTree.SetIfNotExistFuncLock(key, f)
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.
@ -104,22 +124,34 @@ 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.lazyInit()
return tree.BKVTree.Get(key)
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 *BTree) GetOrSet(key any, value any) any {
tree.lazyInit()
return tree.BKVTree.GetOrSet(key, value)
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 *BTree) GetOrSetFunc(key any, f func() any) any {
tree.lazyInit()
return tree.BKVTree.GetOrSetFunc(key, f)
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
@ -127,8 +159,13 @@ 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.lazyInit()
return tree.BKVTree.GetOrSetFuncLock(key, f)
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`.
@ -136,8 +173,7 @@ func (tree *BTree) GetOrSetFuncLock(key any, f func() any) any {
//
// Also see function Get.
func (tree *BTree) GetVar(key any) *gvar.Var {
tree.lazyInit()
return tree.BKVTree.GetVar(key)
return gvar.New(tree.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
@ -145,8 +181,7 @@ func (tree *BTree) GetVar(key any) *gvar.Var {
//
// Also see function GetOrSet.
func (tree *BTree) GetVarOrSet(key any, value any) *gvar.Var {
tree.lazyInit()
return tree.BKVTree.GetVarOrSet(key, value)
return gvar.New(tree.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
@ -154,8 +189,7 @@ 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 {
tree.lazyInit()
return tree.BKVTree.GetVarOrSetFunc(key, f)
return gvar.New(tree.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
@ -163,123 +197,155 @@ 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 {
tree.lazyInit()
return tree.BKVTree.GetVarOrSetFuncLock(key, f)
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 *BTree) Search(key any) (value any, found bool) {
tree.lazyInit()
return tree.BKVTree.Search(key)
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 *BTree) Contains(key any) bool {
tree.lazyInit()
return tree.BKVTree.Contains(key)
tree.mu.RLock()
defer tree.mu.RUnlock()
_, ok := tree.doGet(key)
return ok
}
// Size returns number of nodes in the tree.
func (tree *BTree) Size() int {
tree.lazyInit()
return tree.BKVTree.Size()
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.tree.Size()
}
// IsEmpty returns true if tree does not contain any nodes
func (tree *BTree) IsEmpty() bool {
tree.lazyInit()
return tree.BKVTree.IsEmpty()
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 *BTree) Remove(key any) (value any) {
tree.lazyInit()
return tree.BKVTree.Remove(key)
tree.mu.Lock()
defer tree.mu.Unlock()
return tree.doRemove(key)
}
// Removes batch deletes key-value pairs from the tree by `keys`.
func (tree *BTree) Removes(keys []any) {
tree.lazyInit()
tree.BKVTree.Removes(keys)
tree.mu.Lock()
defer tree.mu.Unlock()
for _, key := range keys {
tree.doRemove(key)
}
}
// Clear removes all nodes from the tree.
func (tree *BTree) Clear() {
tree.lazyInit()
tree.BKVTree.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 *BTree) Keys() []any {
tree.lazyInit()
return tree.BKVTree.Keys()
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 *BTree) Values() []any {
tree.lazyInit()
return tree.BKVTree.Values()
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 *BTree) Replace(data map[any]any) {
tree.lazyInit()
tree.BKVTree.Replace(data)
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 *BTree) Map() map[any]any {
tree.lazyInit()
return tree.BKVTree.Map()
m := make(map[any]any, tree.Size())
tree.IteratorAsc(func(key, value any) bool {
m[key] = value
return true
})
return m
}
// MapStrAny returns all key-value items as map[string]any.
func (tree *BTree) MapStrAny() map[string]any {
tree.lazyInit()
return tree.BKVTree.MapStrAny()
m := make(map[string]any, tree.Size())
tree.IteratorAsc(func(key, value any) bool {
m[gconv.String(key)] = value
return true
})
return m
}
// Print prints the tree to stdout.
func (tree *BTree) Print() {
tree.lazyInit()
tree.BKVTree.Print()
fmt.Println(tree.String())
}
// String returns a string representation of container (for debugging purposes)
func (tree *BTree) String() string {
tree.lazyInit()
return tree.BKVTree.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 *BTree) MarshalJSON() (jsonBytes []byte, err error) {
tree.lazyInit()
return tree.BKVTree.MarshalJSON()
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.tree.MarshalJSON()
}
// Iterator is alias of IteratorAsc.
//
// Also see IteratorAsc.
func (tree *BTree) Iterator(f func(key, value any) bool) {
tree.lazyInit()
tree.BKVTree.Iterator(f)
tree.IteratorAsc(f)
}
// IteratorFrom is alias of IteratorAscFrom.
//
// Also see IteratorAscFrom.
func (tree *BTree) IteratorFrom(key any, match bool, f func(key, value any) bool) {
tree.lazyInit()
tree.BKVTree.IteratorFrom(key, match, f)
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 *BTree) IteratorAsc(f func(key, value any) bool) {
tree.lazyInit()
tree.BKVTree.IteratorAsc(f)
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`.
@ -289,16 +355,34 @@ 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.lazyInit()
tree.BKVTree.IteratorAscFrom(key, match, f)
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]))
}
}
// 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.lazyInit()
tree.BKVTree.IteratorDesc(f)
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`.
@ -308,24 +392,78 @@ 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.lazyInit()
tree.BKVTree.IteratorDescFrom(key, match, f)
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]))
}
}
// Height returns the height of the tree.
func (tree *BTree) Height() int {
tree.lazyInit()
return tree.BKVTree.Height()
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 *BTree) Left() *BTreeEntry {
tree.lazyInit()
return tree.BKVTree.Left()
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,
}
}
// 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.lazyInit()
return tree.BKVTree.Right()
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
}

View File

@ -1,539 +0,0 @@
// 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

@ -1,474 +0,0 @@
// 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

@ -1,613 +0,0 @@
// 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,9 +7,15 @@
package gtree
import (
"sync"
"fmt"
"github.com/emirpasic/gods/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"
)
@ -17,19 +23,25 @@ var _ iTree = (*RedBlackTree)(nil)
// RedBlackTree holds elements of the red-black tree.
type RedBlackTree struct {
*RedBlackKVTree[any, any]
once sync.Once
mu rwmutex.RWMutex
comparator func(v1, v2 any) int
tree *redblacktree.Tree
}
// RedBlackTreeNode is a single element within the tree.
type RedBlackTreeNode = RedBlackKVTreeNode[any, any]
type RedBlackTreeNode struct {
Key any
Value 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{
RedBlackKVTree: NewRedBlackKVTree[any, any](comparator, safe...),
mu: rwmutex.Create(safe...),
comparator: comparator,
tree: redblacktree.NewWith(comparator),
}
}
@ -37,61 +49,71 @@ 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 {
return &RedBlackTree{
RedBlackKVTree: NewRedBlackKVTreeFrom(comparator, data, safe...),
tree := NewRedBlackTree(comparator, safe...)
for k, v := range data {
tree.doSet(k, v)
}
}
// 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)
}
})
return tree
}
// SetComparator sets/changes the comparator for sorting.
func (tree *RedBlackTree) SetComparator(comparator func(a, b any) int) {
tree.lazyInit()
tree.RedBlackKVTree.SetComparator(comparator)
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)
}
}
// Clone clones and returns a new tree from current tree.
func (tree *RedBlackTree) Clone() *RedBlackTree {
if tree == nil {
return nil
}
tree.lazyInit()
return &RedBlackTree{
RedBlackKVTree: tree.RedBlackKVTree.Clone(),
}
newTree := NewRedBlackTree(tree.comparator, tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
// Set sets key-value pair into the tree.
func (tree *RedBlackTree) Set(key any, value any) {
tree.lazyInit()
tree.RedBlackKVTree.Set(key, value)
tree.mu.Lock()
defer tree.mu.Unlock()
tree.doSet(key, value)
}
// Sets batch sets key-values to the tree.
func (tree *RedBlackTree) Sets(data map[any]any) {
tree.lazyInit()
tree.RedBlackKVTree.Sets(data)
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 *RedBlackTree) SetIfNotExist(key any, value any) bool {
tree.lazyInit()
return tree.RedBlackKVTree.SetIfNotExist(key, value)
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 *RedBlackTree) SetIfNotExistFunc(key any, f func() any) bool {
tree.lazyInit()
return tree.RedBlackKVTree.SetIfNotExistFunc(key, f)
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.
@ -100,8 +122,13 @@ 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.lazyInit()
return tree.RedBlackKVTree.SetIfNotExistFuncLock(key, f)
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.
@ -109,22 +136,32 @@ 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) {
tree.lazyInit()
return tree.RedBlackKVTree.Get(key)
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 *RedBlackTree) GetOrSet(key any, value any) any {
tree.lazyInit()
return tree.RedBlackKVTree.GetOrSet(key, value)
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 *RedBlackTree) GetOrSetFunc(key any, f func() any) any {
tree.lazyInit()
return tree.RedBlackKVTree.GetOrSetFunc(key, f)
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
@ -132,8 +169,13 @@ 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.lazyInit()
return tree.RedBlackKVTree.GetOrSetFuncLock(key, f)
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`.
@ -141,8 +183,7 @@ func (tree *RedBlackTree) GetOrSetFuncLock(key any, f func() any) any {
//
// Also see function Get.
func (tree *RedBlackTree) GetVar(key any) *gvar.Var {
tree.lazyInit()
return tree.RedBlackKVTree.GetVar(key)
return gvar.New(tree.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
@ -150,8 +191,7 @@ func (tree *RedBlackTree) GetVar(key any) *gvar.Var {
//
// Also see function GetOrSet.
func (tree *RedBlackTree) GetVarOrSet(key any, value any) *gvar.Var {
tree.lazyInit()
return tree.RedBlackKVTree.GetVarOrSet(key, value)
return gvar.New(tree.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
@ -159,8 +199,7 @@ 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 {
tree.lazyInit()
return tree.RedBlackKVTree.GetVarOrSetFunc(key, f)
return gvar.New(tree.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
@ -168,123 +207,158 @@ 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 {
tree.lazyInit()
return tree.RedBlackKVTree.GetVarOrSetFuncLock(key, f)
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 *RedBlackTree) Search(key any) (value any, found bool) {
tree.lazyInit()
return tree.RedBlackKVTree.Search(key)
tree.mu.RLock()
defer tree.mu.RUnlock()
if node, found := tree.doGet(key); found {
return node, true
}
return nil, false
}
// Contains checks and returns whether given `key` exists in the tree.
func (tree *RedBlackTree) Contains(key any) bool {
tree.lazyInit()
return tree.RedBlackKVTree.Contains(key)
tree.mu.RLock()
defer tree.mu.RUnlock()
_, ok := tree.doGet(key)
return ok
}
// Size returns number of nodes in the tree.
func (tree *RedBlackTree) Size() int {
tree.lazyInit()
return tree.RedBlackKVTree.Size()
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.tree.Size()
}
// IsEmpty returns true if tree does not contain any nodes.
func (tree *RedBlackTree) IsEmpty() bool {
tree.lazyInit()
return tree.RedBlackKVTree.IsEmpty()
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 *RedBlackTree) Remove(key any) (value any) {
tree.lazyInit()
return tree.RedBlackKVTree.Remove(key)
tree.mu.Lock()
defer tree.mu.Unlock()
return tree.doRemove(key)
}
// Removes batch deletes key-value pairs from the tree by `keys`.
func (tree *RedBlackTree) Removes(keys []any) {
tree.lazyInit()
tree.RedBlackKVTree.Removes(keys)
tree.mu.Lock()
defer tree.mu.Unlock()
for _, key := range keys {
tree.doRemove(key)
}
}
// Clear removes all nodes from the tree.
func (tree *RedBlackTree) Clear() {
tree.lazyInit()
tree.RedBlackKVTree.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 *RedBlackTree) Keys() []any {
tree.lazyInit()
return tree.RedBlackKVTree.Keys()
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 *RedBlackTree) Values() []any {
tree.lazyInit()
return tree.RedBlackKVTree.Values()
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 *RedBlackTree) Replace(data map[any]any) {
tree.lazyInit()
tree.RedBlackKVTree.Replace(data)
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 *RedBlackTree) Print() {
tree.lazyInit()
tree.RedBlackKVTree.Print()
fmt.Println(tree.String())
}
// String returns a string representation of container
func (tree *RedBlackTree) String() string {
tree.lazyInit()
return tree.RedBlackKVTree.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 RedBlackTree) MarshalJSON() (jsonBytes []byte, err error) {
tree.lazyInit()
return tree.RedBlackKVTree.MarshalJSON()
func (tree *RedBlackTree) MarshalJSON() (jsonBytes []byte, err error) {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.tree.MarshalJSON()
}
// Map returns all key-value pairs as map.
func (tree *RedBlackTree) Map() map[any]any {
tree.lazyInit()
return tree.RedBlackKVTree.Map()
m := make(map[any]any, tree.Size())
tree.IteratorAsc(func(key, value any) bool {
m[key] = value
return true
})
return m
}
// MapStrAny returns all key-value items as map[string]any.
func (tree *RedBlackTree) MapStrAny() map[string]any {
tree.lazyInit()
return tree.RedBlackKVTree.MapStrAny()
m := make(map[string]any, tree.Size())
tree.IteratorAsc(func(key, value any) bool {
m[gconv.String(key)] = value
return true
})
return m
}
// Iterator is alias of IteratorAsc.
//
// Also see IteratorAsc.
func (tree *RedBlackTree) Iterator(f func(key, value any) bool) {
tree.lazyInit()
tree.RedBlackKVTree.Iterator(f)
tree.IteratorAsc(f)
}
// IteratorFrom is alias of IteratorAscFrom.
//
// Also see IteratorAscFrom.
func (tree *RedBlackTree) IteratorFrom(key any, match bool, f func(key, value any) bool) {
tree.lazyInit()
tree.RedBlackKVTree.IteratorFrom(key, match, f)
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 *RedBlackTree) IteratorAsc(f func(key, value any) bool) {
tree.lazyInit()
tree.RedBlackKVTree.IteratorAsc(f)
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`.
@ -294,16 +368,34 @@ 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.lazyInit()
tree.RedBlackKVTree.IteratorAscFrom(key, match, f)
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]))
}
}
// 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.lazyInit()
tree.RedBlackKVTree.IteratorDesc(f)
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`.
@ -313,20 +405,44 @@ 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.lazyInit()
tree.RedBlackKVTree.IteratorDescFrom(key, match, f)
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]))
}
}
// 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.lazyInit()
return tree.RedBlackKVTree.Left()
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.tree.Left()
if node == nil {
return nil
}
return &RedBlackTreeNode{
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 *RedBlackTree) Right() *RedBlackTreeNode {
tree.lazyInit()
return tree.RedBlackKVTree.Right()
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.tree.Right()
if node == nil {
return nil
}
return &RedBlackTreeNode{
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.
@ -338,8 +454,16 @@ 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.lazyInit()
return tree.RedBlackKVTree.Floor(key)
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
}
// Ceiling finds ceiling node of the input key, returns the ceiling node or nil if no ceiling node is found.
@ -351,8 +475,16 @@ 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.lazyInit()
return tree.RedBlackKVTree.Ceiling(key)
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
}
// Flip exchanges key-value of the tree to value-key.
@ -361,7 +493,6 @@ 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())
@ -378,12 +509,61 @@ 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.lazyInit()
return tree.RedBlackKVTree.UnmarshalJSON(b)
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
}
// UnmarshalValue is an interface implement which sets any type of value for map.
func (tree *RedBlackTree) UnmarshalValue(value any) (err error) {
tree.lazyInit()
return tree.RedBlackKVTree.UnmarshalValue(value)
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
}

View File

@ -8,11 +8,6 @@ package gvar
import "github.com/gogf/gf/v2/util/gconv"
// Bools converts and returns `v` as []bool.
func (v *Var) Bools() []bool {
return gconv.Bools(v.Val())
}
// Ints converts and returns `v` as []int.
func (v *Var) Ints() []int {
return gconv.Ints(v.Val())

View File

@ -21,24 +21,6 @@ func TestVar_Ints(t *testing.T) {
})
}
func TestVar_Bools(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var arr = []bool{true, false, true, false}
objOne := gvar.New(arr, true)
t.AssertEQ(objOne.Bools(), arr)
})
gtest.C(t, func(t *gtest.T) {
var arr = []int{1, 0, 1, 0}
objOne := gvar.New(arr, true)
t.AssertEQ(objOne.Bools(), []bool{true, false, true, false})
})
gtest.C(t, func(t *gtest.T) {
var arr = []string{"true", "false", "1", "0"}
objOne := gvar.New(arr, true)
t.AssertEQ(objOne.Bools(), []bool{true, false, true, false})
})
}
func TestVar_Uints(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var arr = []int{1, 2, 3, 4, 5}

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.6
github.com/gogf/gf/v2 v2.9.5
)
require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
github.com/emirpasic/gods v1.18.1 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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.6
github.com/gogf/gf/v2 v2.9.5
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/v2 v2.0.0-alpha // indirect
github.com/emirpasic/gods v1.18.1 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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.6
github.com/gogf/gf/v2 v2.9.5
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/v2 v2.0.0-alpha // indirect
github.com/emirpasic/gods v1.18.1 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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.6
github.com/gogf/gf/v2 v2.9.5
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/v2 v2.0.0-alpha // indirect
github.com/emirpasic/gods v1.18.1 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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.6
github.com/gogf/gf/v2 v2.9.5
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/v2 v2.0.0-alpha // indirect
github.com/emirpasic/gods v1.18.1 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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,3 +1,4 @@
[简体中文](README.zh_CN.MD)
# Database drivers
@ -8,15 +9,15 @@ Powerful database drivers for package gdb.
Let's take `mysql` for example.
```shell
go get github.com/gogf/gf/contrib/drivers/mysql/v2@latest
go get -u github.com/gogf/gf/contrib/drivers/mysql/v2
# Easy to copy
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
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
```
Choose and import the driver to your project:
@ -43,7 +44,7 @@ func main() {
## Supported Drivers
### MySQL/MariaDB/TiDB/OceanBase
### MySQL/MariaDB/TiDB
```go
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
@ -69,6 +70,10 @@ import _ "github.com/gogf/gf/contrib/drivers/sqlitecgo/v2"
import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
```
Note:
- It does not support `Replace` features.
### SQL Server
```go
@ -77,6 +82,7 @@ import _ "github.com/gogf/gf/contrib/drivers/mssql/v2"
Note:
- It does not support `Replace` features.
- It supports server version >= `SQL Server2005`
- It ONLY supports datetime2 and datetimeoffset types for auto handling created_at/updated_at/deleted_at columns, because datetime type does not support microseconds precision when column value is passed as string.
@ -110,6 +116,10 @@ Note:
import _ "github.com/gogf/gf/contrib/drivers/dm/v2"
```
Note:
- It does not support `Replace` features.
## Custom Drivers
It's quick and easy, please refer to current driver source.

View File

@ -0,0 +1,124 @@
# 数据库驱动程序
用于gdb包的数据库驱动程序。
## 安装
以 `mysql` 为例。
```shell
go get -u github.com/gogf/gf/contrib/drivers/mysql/v2
# 方便复制
go get -u github.com/gogf/gf/contrib/drivers/clickhouse/v2
go get -u github.com/gogf/gf/contrib/drivers/dm/v2
go get -u github.com/gogf/gf/contrib/drivers/mssql/v2
go get -u github.com/gogf/gf/contrib/drivers/oracle/v2
go get -u github.com/gogf/gf/contrib/drivers/pgsql/v2
go get -u github.com/gogf/gf/contrib/drivers/sqlite/v2
go get -u github.com/gogf/gf/contrib/drivers/sqlitecgo/v2
```
选择并将驱动程序导入到您的项目中:
```go
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
```
通常在 `main.go` 的顶部导入:
```go
package main
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
// 其他导入的包。
)
func main() {
// 主要逻辑。
}
```
## 支持的驱动程序
### MySQL/MariaDB/TiDB
```go
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
```
### SQLite
```go
import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
```
#### cgo 版本
32位Windows请使用cgo版本
```go
import _ "github.com/gogf/gf/contrib/drivers/sqlitecgo/v2"
```
### PostgreSQL
```go
import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
```
注意:
- 不支持 `Replace` 功能。
### SQL Server
```go
import _ "github.com/gogf/gf/contrib/drivers/mssql/v2"
```
注意:
- 不支持 `Replace` 功能。
- 仅支持服务器版本 >= `SQL Server2005`
- 仅支持 datetime2 和 datetimeoffset 类型来自动处理 created_at/updated_at/deleted_at 列,因为 datetime 类型在将列值作为字符串传递时不支持微秒精度。
### Oracle
```go
import _ "github.com/gogf/gf/contrib/drivers/oracle/v2"
```
注意:
- 不支持 `Replace` 功能。
- 不支持 `LastInsertId`。
### ClickHouse
```go
import _ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
```
注意:
- 不支持 `InsertIgnore/InsertGetId` 功能。
- 不支持 `Save/Replace` 功能。
- 不支持 `Transaction` 功能。
- 不支持 `RowsAffected` 功能。
### DM
```go
import _ "github.com/gogf/gf/contrib/drivers/dm/v2"
```
注意:
- 不支持 `Replace` 功能。
## 自定义驱动程序
自定义驱动程序非常快速和简单,您可以参考当前驱动程序的源代码来进行开发。
如果您有关于支持新驱动程序的PRPull Request我们将非常感激地接受您的提交到当前仓库。

View File

@ -16,7 +16,6 @@ import (
)
// DoInsert inserts or updates data for given table.
// The list parameter must contain at least one record, which was previously validated.
func (d *Driver) DoInsert(
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {

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.6
github.com/gogf/gf/v2 v2.9.5
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/v2 v2.0.0-alpha // indirect
github.com/emirpasic/gods v1.18.1 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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

@ -14,7 +14,6 @@ import (
"github.com/gogf/gf/v2/frame/g"
)
// Driver is the driver for dm database.
type Driver struct {
*gdb.Core
}

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(`["]`, "", sql)
newSql, _ = gregex.ReplaceString(`["\n\t]`, "", 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

@ -20,7 +20,6 @@ import (
)
// DoInsert inserts or updates data for given table.
// The list parameter must contain at least one record, which was previously validated.
func (d *Driver) DoInsert(
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
@ -29,85 +28,44 @@ func (d *Driver) DoInsert(
return d.doSave(ctx, link, table, list, option)
case gdb.InsertOptionReplace:
// dm does not support REPLACE INTO syntax, use SAVE instead.
return d.doSave(ctx, link, table, list, option)
case gdb.InsertOptionIgnore:
// dm does not support INSERT IGNORE syntax, use MERGE instead.
return d.doInsertIgnore(ctx, link, table, list, option)
default:
return d.Core.DoInsert(ctx, link, table, list, option)
// TODO:: Should be Supported
return nil, gerror.NewCode(
gcode.CodeNotSupported, `Replace operation is not supported by dm driver`,
)
}
return d.Core.DoInsert(ctx, link, table, list, option)
}
// doSave support upsert for dm
func (d *Driver) doSave(ctx context.Context,
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
return d.doMergeInsert(ctx, link, table, list, option, true)
}
if len(option.OnConflict) == 0 {
return nil, gerror.NewCode(
gcode.CodeMissingParameter, `Please specify conflict columns`,
)
}
// doInsertIgnore implements INSERT IGNORE operation using MERGE statement for DM database.
// It only inserts records when there's no conflict on primary/unique keys.
func (d *Driver) doInsertIgnore(ctx context.Context,
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
return d.doMergeInsert(ctx, link, table, list, option, false)
}
// doMergeInsert implements MERGE-based insert operations for DM database.
// When withUpdate is true, it performs upsert (insert or update).
// When withUpdate is false, it performs insert ignore (insert only when no conflict).
func (d *Driver) doMergeInsert(
ctx context.Context,
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption, withUpdate bool,
) (result sql.Result, err error) {
// If OnConflict is not specified, automatically get the primary key of the table
conflictKeys := option.OnConflict
if len(conflictKeys) == 0 {
primaryKeys, err := d.Core.GetPrimaryKeys(ctx, table)
if err != nil {
return nil, gerror.WrapCode(
gcode.CodeInternalError,
err,
`failed to get primary keys for table`,
)
}
foundPrimaryKey := false
for _, primaryKey := range primaryKeys {
for dataKey := range list[0] {
if strings.EqualFold(dataKey, primaryKey) {
foundPrimaryKey = true
break
}
}
if foundPrimaryKey {
break
}
}
if !foundPrimaryKey {
return nil, gerror.NewCodef(
gcode.CodeMissingParameter,
`Replace/Save/InsertIgnore operation requires conflict detection: `+
`either specify OnConflict() columns or ensure table '%s' has a primary key in the data`,
table,
)
}
conflictKeys = primaryKeys
if len(list) == 0 {
return nil, gerror.NewCode(
gcode.CodeInvalidRequest, `Save operation list is empty by oracle driver`,
)
}
var (
one = list[0]
oneLen = len(one)
charL, charR = d.GetChars()
one = list[0]
oneLen = len(one)
charL, charR = d.GetChars()
conflictKeys = option.OnConflict
conflictKeySet = gset.New(false)
// queryHolders: Handle data with Holder that need to be merged
// queryValues: Handle data that need to be merged
// queryHolders: Handle data with Holder that need to be upsert
// queryValues: Handle data that need to be upsert
// insertKeys: Handle valid keys that need to be inserted
// insertValues: Handle values that need to be inserted
// updateValues: Handle values that need to be updated (only when withUpdate=true)
// updateValues: Handle values that need to be updated
queryHolders = make([]string, oneLen)
queryValues = make([]any, oneLen)
insertKeys = make([]string, oneLen)
@ -128,9 +86,9 @@ func (d *Driver) doMergeInsert(
insertKeys[index] = keyWithChar
insertValues[index] = fmt.Sprintf("T2.%s", keyWithChar)
// Build updateValues only when withUpdate is true
// Filter conflict keys and soft created fields from updateValues
if withUpdate && !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
// filter conflict keys in updateValues.
// And the key is not a soft created field.
if !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
updateValues = append(
updateValues,
fmt.Sprintf(`T1.%s = T2.%s`, keyWithChar, keyWithChar),
@ -139,10 +97,8 @@ func (d *Driver) doMergeInsert(
index++
}
var (
batchResult = new(gdb.SqlResult)
sqlStr = parseSqlForMerge(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
)
batchResult := new(gdb.SqlResult)
sqlStr := parseSqlForUpsert(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
r, err := d.DoExec(ctx, link, sqlStr, queryValues...)
if err != nil {
return r, err
@ -156,40 +112,40 @@ func (d *Driver) doMergeInsert(
return batchResult, nil
}
// parseSqlForMerge generates MERGE statement for DM database.
// When updateValues is empty, it only inserts (INSERT IGNORE behavior).
// When updateValues is provided, it performs upsert (INSERT or UPDATE).
// Examples:
// - INSERT IGNORE: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...)
// - UPSERT: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...) WHEN MATCHED THEN UPDATE SET ...
func parseSqlForMerge(table string,
// parseSqlForUpsert
// MERGE INTO {{table}} T1
// USING ( SELECT {{queryHolders}} FROM DUAL T2
// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}} AND ...)
// WHEN NOT MATCHED THEN
// INSERT {{insertKeys}} VALUES {{insertValues}}
// WHEN MATCHED THEN
// UPDATE SET {{updateValues}}
func parseSqlForUpsert(table string,
queryHolders, insertKeys, insertValues, updateValues, duplicateKey []string,
) (sqlStr string) {
var (
queryHolderStr = strings.Join(queryHolders, ",")
insertKeyStr = strings.Join(insertKeys, ",")
insertValueStr = strings.Join(insertValues, ",")
updateValueStr = strings.Join(updateValues, ",")
duplicateKeyStr string
pattern = gstr.Trim(`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s) WHEN MATCHED THEN UPDATE SET %s;`)
)
// Build ON condition
for index, keys := range duplicateKey {
if index != 0 {
duplicateKeyStr += " AND "
}
duplicateKeyStr += fmt.Sprintf("T1.%s = T2.%s", keys, keys)
duplicateTmp := fmt.Sprintf("T1.%s = T2.%s", keys, keys)
duplicateKeyStr += duplicateTmp
}
// Build SQL based on whether UPDATE is needed
pattern := gstr.Trim(`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s)`)
if len(updateValues) > 0 {
// Upsert: INSERT or UPDATE
pattern += gstr.Trim(`WHEN MATCHED THEN UPDATE SET %s`)
return fmt.Sprintf(
pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr,
strings.Join(updateValues, ","),
)
}
// Insert Ignore: INSERT only
return fmt.Sprintf(pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr)
return fmt.Sprintf(pattern,
table,
queryHolderStr,
duplicateKeyStr,
insertKeyStr,
insertValueStr,
updateValueStr,
)
}

View File

@ -11,21 +11,12 @@ 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 c.COLUMN_NAME, c.DATA_TYPE, c.DATA_LENGTH, c.DATA_DEFAULT, c.NULLABLE, cc.COMMENTS FROM ALL_TAB_COLUMNS c LEFT JOIN ALL_COL_COMMENTS cc ON c.COLUMN_NAME = cc.COLUMN_NAME AND c.TABLE_NAME = cc.TABLE_NAME AND c.OWNER = cc.OWNER WHERE c.TABLE_NAME = '%s' AND c.OWNER = '%s'`
tableFieldsPkSqlSchemaTmp = `SELECT COLS.COLUMN_NAME AS PRIMARY_KEY_COLUMN FROM USER_CONSTRAINTS CONS JOIN USER_CONS_COLUMNS COLS ON CONS.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME WHERE CONS.TABLE_NAME = '%s' AND CONS.CONSTRAINT_TYPE = 'P'`
tableFieldsPkSqlDBATmp = `SELECT COLS.COLUMN_NAME AS PRIMARY_KEY_COLUMN FROM DBA_CONSTRAINTS CONS JOIN DBA_CONS_COLUMNS COLS ON CONS.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME WHERE CONS.TABLE_NAME = '%s' AND CONS.OWNER = '%s' AND CONS.CONSTRAINT_TYPE = 'P'`
tableFieldsSqlTmp = `SELECT * FROM ALL_TAB_COLUMNS WHERE Table_Name= '%s' AND OWNER = '%s'`
)
// TableFields retrieves and returns the fields' information of specified table of current schema.
@ -33,9 +24,8 @@ func (d *Driver) TableFields(
ctx context.Context, table string, schema ...string,
) (fields map[string]*gdb.TableField, err error) {
var (
result gdb.Result
pkResult gdb.Result
link gdb.Link
result gdb.Result
link gdb.Link
// When no schema is specified, the configuration item is returned by default
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
)
@ -48,35 +38,14 @@ func (d *Driver) TableFields(
ctx, link,
fmt.Sprintf(
tableFieldsSqlTmp,
escapeSingleQuote(strings.ToUpper(table)),
escapeSingleQuote(strings.ToUpper(d.GetSchema())),
strings.ToUpper(table),
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
@ -85,29 +54,15 @@ func (d *Driver) TableFields(
if m["NULLABLE"].String() != "N" {
nullable = true
}
// Build field type with length/precision
// For NUMBER(p,s): use DATA_PRECISION and DATA_SCALE
// For VARCHAR2/CHAR: use DATA_LENGTH
var (
fieldType string
dataType = m["DATA_TYPE"].String()
dataLength = m["DATA_LENGTH"].Int()
)
if dataLength > 0 {
fieldType = fmt.Sprintf("%s(%d)", dataType, dataLength)
} else {
fieldType = dataType
}
fields[m["COLUMN_NAME"].String()] = &gdb.TableField{
Index: i,
Name: m["COLUMN_NAME"].String(),
Type: fieldType,
Type: m["DATA_TYPE"].String(),
Null: nullable,
Default: m["DATA_DEFAULT"].Val(),
Key: pkFields.Get(m["COLUMN_NAME"].String()),
// Key: m["Key"].String(),
// Extra: m["Extra"].String(),
Comment: m["COMMENTS"].String(),
// Comment: m["Comment"].String(),
}
}
return fields, nil

View File

@ -28,7 +28,10 @@ func Test_DB_Ping(t *testing.T) {
}
func TestTables(t *testing.T) {
tables := createInitTables(2)
tables := []string{"A_tables", "A_tables2"}
for _, v := range tables {
createInitTable(v)
}
gtest.C(t, func(t *gtest.T) {
result, err := db.Tables(ctx)
gtest.AssertNil(err)
@ -36,7 +39,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]) == strings.ToUpper(result[j]) {
if strings.ToUpper(tables[i]) == result[j] {
find = true
break
}
@ -49,7 +52,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]) == strings.ToUpper(result[j]) {
if strings.ToUpper(tables[i]) == result[j] {
find = true
break
}
@ -80,14 +83,17 @@ func TestTableFields(t *testing.T) {
createInitTable(tables)
gtest.C(t, func(t *gtest.T) {
var expect = map[string][]any{
"ID": {"BIGINT(8)", false},
"ACCOUNT_NAME": {"VARCHAR(128)", false},
"PWD_RESET": {"TINYINT(1)", false},
"ATTR_INDEX": {"INT(4)", true},
"DELETED": {"INT(4)", false},
"CREATED_TIME": {"TIMESTAMP(8)", false},
"ID": {"BIGINT", false},
"ACCOUNT_NAME": {"VARCHAR", false},
"PWD_RESET": {"TINYINT", false},
"ATTR_INDEX": {"INT", true},
"DELETED": {"INT", false},
"CREATED_TIME": {"TIMESTAMP", false},
}
_, err := dbErr.TableFields(ctx, "Fields")
gtest.AssertNE(err, nil)
res, err := db.TableFields(ctx, tables)
gtest.AssertNil(err)
@ -108,14 +114,6 @@ 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)
@ -140,6 +138,110 @@ func Test_DB_Query(t *testing.T) {
})
}
func TestModelSave(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
AccountName string
AttrIndex int
}
var (
user User
count int
result sql.Result
err error
)
db.SetDebug(true)
result, err = db.Model(table).Data(g.Map{
"id": 1,
"accountName": "ac1",
"attrIndex": 100,
}).OnConflict("id").Save()
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.AccountName, "ac1")
t.Assert(user.AttrIndex, 100)
_, err = db.Model(table).Data(g.Map{
"id": 1,
"accountName": "ac2",
"attrIndex": 200,
}).OnConflict("id").Save()
t.AssertNil(err)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.AccountName, "ac2")
t.Assert(user.AttrIndex, 200)
count, err = db.Model(table).Count()
t.AssertNil(err)
t.Assert(count, 1)
})
}
func TestModelInsert(t *testing.T) {
// g.Model.insert not lost default not null coloumn
table := "A_tables"
createInitTable(table)
gtest.C(t, func(t *gtest.T) {
i := 200
data := User{
ID: int64(i),
AccountName: fmt.Sprintf(`A%dtwo`, i),
PwdReset: 0,
AttrIndex: 99,
CreatedTime: time.Now(),
UpdatedTime: time.Now(),
}
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
_, err := db.Model(table).Insert(&data)
gtest.AssertNil(err)
})
gtest.C(t, func(t *gtest.T) {
i := 201
data := User{
ID: int64(i),
AccountName: fmt.Sprintf(`A%dtwoONE`, i),
PwdReset: 1,
CreatedTime: time.Now(),
AttrIndex: 98,
UpdatedTime: time.Now(),
}
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
_, err := db.Model(table).Data(&data).Insert()
gtest.AssertNil(err)
})
}
func TestDBInsert(t *testing.T) {
table := "A_tables"
createInitTable("A_tables")
gtest.C(t, func(t *gtest.T) {
i := 300
data := g.Map{
"ID": i,
"ACCOUNT_NAME": fmt.Sprintf(`A%dthress`, i),
"PWD_RESET": 3,
"ATTR_INDEX": 98,
"CREATED_TIME": gtime.Now(),
"UPDATED_TIME": gtime.Now(),
}
_, err := db.Insert(ctx, table, &data)
gtest.AssertNil(err)
})
}
func Test_DB_Exec(t *testing.T) {
createInitTable("A_tables")
gtest.C(t, func(t *gtest.T) {
@ -509,124 +611,3 @@ func Test_Empty_Slice_Argument(t *testing.T) {
t.Assert(len(result), 0)
})
}
func TestModelSave(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
AccountName string
AttrIndex int
}
var (
user User
count int
result sql.Result
err error
)
result, err = db.Model(table).Data(g.Map{
"id": 1,
"accountName": "ac1",
"attrIndex": 100,
}).OnConflict("id").Save()
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.AccountName, "ac1")
t.Assert(user.AttrIndex, 100)
_, err = db.Model(table).Data(g.Map{
"id": 1,
"accountName": "ac2",
"attrIndex": 200,
}).OnConflict("id").Save()
t.AssertNil(err)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.AccountName, "ac2")
t.Assert(user.AttrIndex, 200)
count, err = db.Model(table).Count()
t.AssertNil(err)
t.Assert(count, 1)
})
}
func TestModelInsert(t *testing.T) {
// g.Model.insert not lost default not null column
table := "A_tables"
createInitTable(table)
gtest.C(t, func(t *gtest.T) {
i := 200
data := User{
ID: int64(i),
AccountName: fmt.Sprintf(`A%dtwo`, i),
PwdReset: 0,
AttrIndex: 99,
CreatedTime: time.Now(),
UpdatedTime: time.Now(),
}
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
_, err := db.Model(table).Insert(&data)
gtest.AssertNil(err)
})
gtest.C(t, func(t *gtest.T) {
i := 201
data := User{
ID: int64(i),
AccountName: fmt.Sprintf(`A%dtwoONE`, i),
PwdReset: 1,
CreatedTime: time.Now(),
AttrIndex: 98,
UpdatedTime: time.Now(),
}
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
_, err := db.Model(table).Data(&data).Insert()
gtest.AssertNil(err)
})
}
func Test_Model_InsertIgnore(t *testing.T) {
table := createInitTable()
defer dropTable(table)
// db.SetDebug(true)
gtest.C(t, func(t *gtest.T) {
data := User{
ID: int64(666),
AccountName: fmt.Sprintf(`name_%d`, 666),
PwdReset: 0,
AttrIndex: 99,
CreatedTime: time.Now(),
UpdatedTime: time.Now(),
}
_, err := db.Model(table).Data(data).Insert()
t.AssertNil(err)
})
gtest.C(t, func(t *gtest.T) {
data := User{
ID: int64(666),
AccountName: fmt.Sprintf(`name_%d`, 777),
PwdReset: 0,
AttrIndex: 99,
CreatedTime: time.Now(),
UpdatedTime: time.Now(),
}
_, err := db.Model(table).Data(data).InsertIgnore()
t.AssertNil(err)
one, err := db.Model(table).Where("id", 666).One()
t.AssertNil(err)
t.Assert(one["ACCOUNT_NAME"].String(), "name_666")
})
}

File diff suppressed because it is too large Load Diff

View File

@ -63,10 +63,11 @@ func init() {
Weight: 1,
MaxIdleConnCount: 10,
MaxOpenConnCount: 10,
// CreatedAt: "created_time",
// UpdatedAt: "updated_time",
CreatedAt: "created_time",
UpdatedAt: "updated_time",
}
// todo
nodeLink := gdb.ConfigNode{
Type: TestDBType,
Name: TestDBName,
@ -110,8 +111,6 @@ func init() {
}
ctx = context.Background()
// db.SetDebug(true)
}
func dropTable(table string) {
@ -144,7 +143,7 @@ func createTable(table ...string) (name string) {
CREATE TABLE "%s"
(
"ID" BIGINT NOT NULL,
"ACCOUNT_NAME" VARCHAR(128) DEFAULT '' NOT NULL COMMENT 'Account Name',
"ACCOUNT_NAME" VARCHAR(128) DEFAULT '' NOT NULL,
"PWD_RESET" TINYINT DEFAULT 0 NOT NULL,
"ENABLED" INT DEFAULT 1 NOT NULL,
"DELETED" INT DEFAULT 0 NOT NULL,
@ -157,6 +156,7 @@ 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,
"created_time": gtime.Now(),
"create_time": gtime.Now().String(),
})
}
result, err := db.Schema(TestDBName).Insert(context.Background(), name, array.Slice())
@ -212,11 +212,3 @@ 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,93 +71,3 @@ 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

@ -1,40 +0,0 @@
// 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.6
github.com/gogf/gf/v2 v2.9.5
)
require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
github.com/emirpasic/gods v1.18.1 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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.6
github.com/gogf/gf/v2 v2.9.5
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/v2 v2.0.0-alpha // indirect
github.com/emirpasic/gods v1.18.1 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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,24 +13,17 @@ import (
)
const (
// INSERT statement prefixes
insertPrefixDefault = "INSERT INTO"
insertPrefixIgnore = "INSERT IGNORE INTO"
backIdInsertHeadDefault = "INSERT INTO"
backIdInsertHeadInsertIgnore = "INSERT IGNORE INTO"
// 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"
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
)
// DoExec commits the sql string and its arguments to underlying driver
@ -41,7 +34,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.MasterLink(); err != nil {
} else if link, err = d.Core.MasterLink(); err != nil {
// Or else it creates one from master node.
return nil, err
}
@ -53,17 +46,17 @@ func (d *Driver) DoExec(ctx context.Context, link gdb.Link, sqlStr string, args
}
// SQL filtering.
sqlStr, args = d.FormatSqlBeforeExecuting(sqlStr, args)
sqlStr, args = d.Core.FormatSqlBeforeExecuting(sqlStr, args)
sqlStr, args, err = d.DoFilter(ctx, link, sqlStr, args)
if err != nil {
return nil, err
}
if !strings.HasPrefix(sqlStr, insertPrefixDefault) && !strings.HasPrefix(sqlStr, insertPrefixIgnore) {
if !(strings.HasPrefix(sqlStr, backIdInsertHeadDefault) || strings.HasPrefix(sqlStr, backIdInsertHeadInsertIgnore)) {
return d.Core.DoExec(ctx, link, sqlStr, args)
}
// Find the first position of VALUES marker in the INSERT statement.
pos := strings.Index(sqlStr, insertValuesMarker)
// find the first pos
pos := strings.Index(sqlStr, positionInsertValues)
table := d.GetTableNameFromSql(sqlStr)
outPutSql := d.GetInsertOutputSql(ctx, table)
@ -89,18 +82,21 @@ 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
}
// 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()
// get affect count
aCount = stdSqlResult[0].GMap().GetVar(affectCountFieldName).Int64()
// get last_insert_id
lId = stdSqlResult[0].GMap().GetVar(fdId).Int64()
return &InsertResult{lastInsertId: lastInsertId, rowsAffected: rowsAffected}, err
return &InsertResult{lastInsertId: lId, rowsAffected: aCount}, err
}
// GetTableNameFromSql get table name from sql statement
@ -116,7 +112,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, ".") {
@ -167,24 +163,24 @@ func (r *InsertResult) RowsAffected() (int64, error) {
}
// GetInsertOutputSql gen get last_insert_id code
func (d *Driver) GetInsertOutputSql(ctx context.Context, table string) string {
fds, errFd := d.GetDB().TableFields(ctx, table)
func (m *Driver) GetInsertOutputSql(ctx context.Context, table string) string {
fds, errFd := m.GetDB().TableFields(ctx, table)
if errFd != nil {
return ""
}
extraSqlAry := make([]string, 0)
extraSqlAry = append(extraSqlAry, fmt.Sprintf(" %s %s", outputKeyword, affectCountExpression))
extraSqlAry = append(extraSqlAry, fmt.Sprintf(" %s %s", mssqlOutPutKey, mssqlAffectFd))
incrNo := 0
if len(fds) > 0 {
for _, fd := range fds {
// has primary key and is auto-increment
if fd.Extra == fieldExtraIdentity && fd.Key == fieldKeyPrimary && !fd.Null {
if fd.Extra == autoIncrementName && fd.Key == mssqlPrimaryKeyName && !fd.Null {
incrNoStr := ""
if incrNo == 0 { // fixed first field named id, convenient to get
incrNoStr = fmt.Sprintf(" as %s", lastInsertIdFieldAlias)
incrNoStr = fmt.Sprintf(" as %s", fdId)
}
extraSqlAry = append(extraSqlAry, fmt.Sprintf("%s.%s%s", insertedObjectName, fd.Name, incrNoStr))
extraSqlAry = append(extraSqlAry, fmt.Sprintf("%s.%s%s", mssqlInsertedObjName, 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,48 +8,49 @@ 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) {
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, "")
})
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)
}
})
}
}
func TestDriver_handleSelectSqlReplacement(t *testing.T) {

View File

@ -20,51 +20,17 @@ import (
)
// DoInsert inserts or updates data for given table.
// The list parameter must contain at least one record, which was previously validated.
func (d *Driver) DoInsert(
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
switch option.InsertOption {
case
gdb.InsertOptionSave,
gdb.InsertOptionReplace:
// MSSQL does not support REPLACE INTO syntax.
// Convert Replace to Save operation, using MERGE statement.
// Auto-detect primary keys if OnConflict is not specified.
if len(option.OnConflict) == 0 {
primaryKeys, err := d.Core.GetPrimaryKeys(ctx, table)
if err != nil {
return nil, gerror.WrapCode(
gcode.CodeInternalError,
err,
`failed to get primary keys for Save/Replace operation`,
)
}
foundPrimaryKey := false
for _, primaryKey := range primaryKeys {
for dataKey := range list[0] {
if strings.EqualFold(dataKey, primaryKey) {
foundPrimaryKey = true
break
}
}
if foundPrimaryKey {
break
}
}
if !foundPrimaryKey {
return nil, gerror.NewCodef(
gcode.CodeMissingParameter,
`Save/Replace operation requires conflict detection: `+
`either specify OnConflict() columns or ensure table '%s' has a primary key in the data`,
table,
)
}
option.OnConflict = primaryKeys
}
// Convert to Save operation
case gdb.InsertOptionSave:
return d.doSave(ctx, link, table, list, option)
case gdb.InsertOptionReplace:
return nil, gerror.NewCode(
gcode.CodeNotSupported,
`Replace operation is not supported by mssql driver`,
)
default:
return d.Core.DoInsert(ctx, link, table, list, option)
}
@ -74,10 +40,23 @@ func (d *Driver) DoInsert(
func (d *Driver) doSave(ctx context.Context,
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
if len(option.OnConflict) == 0 {
return nil, gerror.NewCode(
gcode.CodeMissingParameter, `Please specify conflict columns`,
)
}
if len(list) == 0 {
return nil, gerror.NewCode(
gcode.CodeInvalidRequest, `Save operation list is empty by mssql driver`,
)
}
var (
one = list[0]
oneLen = len(one)
charL, charR = d.GetChars()
one = list[0]
oneLen = len(one)
charL, charR = d.GetChars()
conflictKeys = option.OnConflict
conflictKeySet = gset.New(false)
@ -148,10 +127,7 @@ func parseSqlForUpsert(table string,
insertValueStr = strings.Join(insertValues, ",")
updateValueStr = strings.Join(updateValues, ",")
duplicateKeyStr string
pattern = gstr.Trim(
`MERGE INTO %s T1 USING (VALUES(%s)) T2 (%s) ON (%s) WHEN NOT MATCHED ` +
`THEN INSERT(%s) VALUES (%s) WHEN MATCHED THEN UPDATE SET %s;`,
)
pattern = gstr.Trim(`MERGE INTO %s T1 USING (VALUES(%s)) T2 (%s) ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s) WHEN MATCHED THEN UPDATE SET %s;`)
)
for index, keys := range duplicateKey {

View File

@ -17,32 +17,32 @@ import (
var (
tableFieldsSqlTmp = `
SELECT
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
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
`
)

View File

@ -13,7 +13,7 @@ import (
)
const (
tablesSqlTmp = `SELECT name FROM sys.objects WHERE type='U' AND is_ms_shipped = 0 ORDER BY name`
tablesSqlTmp = `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 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(), TestSchema)
result, err = db.Tables(context.Background(), "master")
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", TestSchema)
res, err = db.TableFields(context.Background(), "t_user", "master")
gtest.AssertNil(err)
for k, v := range expect {
@ -138,17 +138,15 @@ func TestDoInsert(t *testing.T) {
i := 10
data := g.Map{
// "id": i,
"id": i,
"passport": fmt.Sprintf(`t%d`, i),
"password": fmt.Sprintf(`p%d`, i),
"nickname": fmt.Sprintf(`T%d`, i),
"create_time": gtime.Now(),
}
// Save without OnConflict should fail (missing conflict columns)
_, err := db.Save(context.Background(), "t_user", data, 10)
gtest.AssertNE(err, nil)
// Replace should fail because primary key 'id' is not in the data
_, err = db.Replace(context.Background(), "t_user", data, 10)
gtest.AssertNE(err, nil)
})

View File

@ -27,7 +27,8 @@ var (
const (
TableSize = 10
TableName = "t_user"
TestSchema = "test"
TestSchema1 = "test1"
TestSchema2 = "test2"
TableNamePrefix1 = "gf_"
TestDbUser = "sa"
TestDbPass = "LoremIpsum86"
@ -35,40 +36,12 @@ 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: TestSchema,
Name: "test",
Type: "mssql",
Role: "master",
Charset: "utf8",
@ -127,8 +100,8 @@ func createTable(table ...string) (name string) {
dropTable(name)
if _, err := db.Exec(context.Background(), fmt.Sprintf(`
IF NOT EXISTS (SELECT * FROM sys.objects WHERE name='%s' and type='U')
CREATE TABLE [%s] (
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='%s' and xtype='U')
CREATE TABLE %s (
ID numeric(10,0) NOT NULL,
PASSPORT VARCHAR(45) NULL,
PASSWORD VARCHAR(32) NULL,
@ -141,6 +114,7 @@ func createTable(table ...string) (name string) {
gtest.Fatal(err)
}
db.Schema("test")
return
}
@ -167,18 +141,18 @@ func createInitTable(table ...string) (name string) {
func dropTable(table string) {
if _, err := db.Exec(context.Background(), fmt.Sprintf(`
IF EXISTS (SELECT * FROM sys.objects WHERE name='%s' and type='U')
DROP TABLE [%s]
IF EXISTS (SELECT * FROM sysobjects WHERE name='%s' and xtype='U')
DROP TABLE %s
`, table, table)); err != nil {
gtest.Fatal(err)
}
}
// createInsertAndGetIdTableForTest tests InsertAndGetId functionality
// createInsertAndGetIdTableForTest test for InsertAndGetId
func createInsertAndGetIdTableForTest() (name string) {
if _, err := db.Exec(context.Background(), `
IF NOT EXISTS (SELECT * FROM sys.objects WHERE name='ip_to_id' and type='U')
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='ip_to_id' and xtype='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,6 +61,7 @@ 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",
@ -70,6 +71,7 @@ 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",
@ -79,6 +81,7 @@ 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"`
@ -87,6 +90,7 @@ 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",
@ -99,6 +103,7 @@ func Test_Model_Insert(t *testing.T) {
result, err = db.Model(table).Data(&User{
Id: 4,
Uid: 4,
Passport: "t4",
Password: "25d55ad283aa400af464c76d713c07ad",
Nickname: "T4",
@ -208,6 +213,7 @@ 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),
@ -229,6 +235,7 @@ 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",
@ -236,6 +243,7 @@ func Test_Model_Batch(t *testing.T) {
},
{
"id": 3,
"uid": 3,
"passport": "t3",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_3",
@ -1599,6 +1607,7 @@ 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)
@ -2658,131 +2667,13 @@ func Test_Model_Replace(t *testing.T) {
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
// Insert initial record
result, err := db.Model(table).Data(g.Map{
"id": 1,
"passport": "t1",
"password": "pass1",
"nickname": "T1",
"create_time": "2018-10-24 10:00:00",
}).Insert()
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
// Replace with new data (should update existing record using MERGE)
result, err = db.Model(table).Data(g.Map{
_, err := db.Model(table).Data(g.Map{
"id": 1,
"passport": "t11",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T11",
"create_time": "2018-10-24 10:00:00",
}).Replace()
t.AssertNil(err)
n, _ = result.RowsAffected()
t.Assert(n, 1)
// Verify the data was replaced
one, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["PASSPORT"].String(), "t11")
t.Assert(one["NICKNAME"].String(), "T11")
// Replace with non-existing record (should insert new record)
result, err = db.Model(table).Data(g.Map{
"id": 2,
"passport": "t222",
"password": "pass2",
"nickname": "T222",
"create_time": "2018-10-24 11:00:00",
}).Replace()
t.AssertNil(err)
n, _ = result.RowsAffected()
t.Assert(n, 1) // MERGE reports: 1 for insert
// Verify the new record was inserted
one, err = db.Model(table).WherePri(2).One()
t.AssertNil(err)
t.Assert(one["PASSPORT"].String(), "t222")
t.Assert(one["NICKNAME"].String(), "T222")
})
}
// 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)
t.Assert(err, "Replace operation is not supported by mssql driver")
})
}

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.6
github.com/gogf/gf/v2 v2.9.5
)
require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
github.com/emirpasic/gods v1.18.1 // 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/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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

@ -8,13 +8,60 @@ package mysql
import (
"context"
"strings"
"github.com/gogf/gf/v2/database/gdb"
)
// DoFilter handles the sql before posts it to database.
// This method helps handle MySQL-specific issues including key length limitations.
//
// For MySQL tables using utf8mb4 charset, this method automatically adds ROW_FORMAT=DYNAMIC
// to CREATE TABLE statements to prevent "Specified key was too long; max key length is 1000 bytes" errors.
// This is particularly important for compatibility when upgrading from older GoFrame versions.
func (d *Driver) DoFilter(
ctx context.Context, link gdb.Link, sql string, args []any,
) (newSql string, newArgs []any, err error) {
return d.Core.DoFilter(ctx, link, sql, args)
newSql, newArgs, err = d.Core.DoFilter(ctx, link, sql, args)
if err != nil {
return newSql, newArgs, err
}
// Handle MySQL-specific SQL filtering to prevent key length issues
// This is particularly important for compatibility between GoFrame versions
newSql = d.handleMySQLKeyLengthCompatibility(newSql)
return newSql, newArgs, err
}
// handleMySQLKeyLengthCompatibility modifies SQL statements to be more compatible
// with MySQL key length limitations, especially for upgrade scenarios from older GoFrame versions
func (d *Driver) handleMySQLKeyLengthCompatibility(sql string) string {
// For CREATE TABLE statements with utf8mb4 charset, ensure key length compatibility
// This helps prevent "Specified key was too long; max key length is 1000 bytes" errors
sqlUpper := strings.ToUpper(sql)
sqlLower := strings.ToLower(sql)
if strings.Contains(sqlUpper, "CREATE TABLE") &&
(strings.Contains(sqlLower, "utf8mb4") || strings.Contains(sqlLower, "charset=utf8mb4")) {
// Add ROW_FORMAT=DYNAMIC to enable larger key prefixes when using utf8mb4
if !strings.Contains(sqlUpper, "ROW_FORMAT") {
// Insert ROW_FORMAT=DYNAMIC before ENGINE clause if it exists
if strings.Contains(sqlUpper, "ENGINE=") {
sql = strings.Replace(sql, "ENGINE=", "ROW_FORMAT=DYNAMIC ENGINE=", 1)
} else if strings.Contains(sqlUpper, "ENGINE ") {
// Handle case where there's a space after ENGINE
sql = strings.Replace(sql, "ENGINE ", "ROW_FORMAT=DYNAMIC ENGINE ", 1)
} else {
// Append ROW_FORMAT=DYNAMIC at the end of CREATE TABLE statement
sql = strings.TrimSuffix(strings.TrimSpace(sql), ";")
sql += " ROW_FORMAT=DYNAMIC"
if !strings.HasSuffix(sql, ";") {
sql += ";"
}
}
}
}
return sql
}

View File

@ -47,12 +47,14 @@ func configNodeToSource(config *gdb.ConfigNode) string {
"%s:%s@%s(%s%s)/%s?charset=%s",
config.User, config.Pass, config.Protocol, config.Host, portStr, config.Name, config.Charset,
)
if config.Timezone != "" {
if strings.Contains(config.Timezone, "/") {
config.Timezone = url.QueryEscape(config.Timezone)
}
source = fmt.Sprintf("%s&loc=%s", source, config.Timezone)
}
if config.Extra != "" {
source = fmt.Sprintf("%s&%s", source, config.Extra)
}

View File

@ -1,236 +0,0 @@
// 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

@ -0,0 +1,47 @@
// Test case for MySQL key length issue #4382
package mysql_test
import (
"testing"
"github.com/gogf/gf/v2/test/gtest"
)
// Test_Issue4382_KeyLengthLimit tests the MySQL key length limitation issue
// This test reproduces the issue reported in #4382 where upgrading from GoFrame 2.6 to 2.9
// causes "Specified key was too long; max key length is 1000 bytes" error
func Test_Issue4382_KeyLengthLimit(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// This test should not fail due to key length limitations
// when using proper MySQL configuration
table := createTable("test_key_length")
defer dropTable(table)
// Try to create a table with potentially long keys (using utf8mb4)
// This scenario could trigger the key length issue
longTableSQL := `
CREATE TABLE test_long_keys (
id INT PRIMARY KEY AUTO_INCREMENT,
long_field_1 VARCHAR(255) CHARACTER SET utf8mb4,
long_field_2 VARCHAR(255) CHARACTER SET utf8mb4,
KEY idx_long_composite (long_field_1, long_field_2)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`
_, err := db.Exec(ctx, "DROP TABLE IF EXISTS test_long_keys")
t.AssertNil(err)
// This should not fail with key length error in GoFrame 2.9
// Our DoFilter enhancement should automatically add ROW_FORMAT=DYNAMIC
_, err = db.Exec(ctx, longTableSQL)
if err != nil {
// If we get the specific key length error, this confirms the issue
// With our fix, this should not happen
t.Logf("Error creating table: %v", err)
}
t.AssertNil(err)
// Clean up
db.Exec(ctx, "DROP TABLE IF EXISTS test_long_keys")
})
}

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